mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-22 12:48:59 +08:00
refactor: 合并 v2 版本业务代码更新
This commit is contained in:
parent
8b22ffdef0
commit
de86041995
@ -1,12 +0,0 @@
|
||||
package com.youlai.mall.oms.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class OrderInfoDTO {
|
||||
|
||||
private String orderSn;
|
||||
|
||||
private Integer status;
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package com.youlai.mall.oms.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SeataOrderDTO {
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 订单金额
|
||||
*/
|
||||
private Long amount;
|
||||
|
||||
|
||||
private Boolean openEx;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.youlai.mall.oms.constant;
|
||||
|
||||
/**
|
||||
* 订单相关常量
|
||||
*
|
||||
* <p>该接口定义了与订单相关的常量。</p>
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface OrderConstants {
|
||||
|
||||
/**
|
||||
* 会员购物车缓存键前缀
|
||||
*/
|
||||
String MEMBER_CART_PREFIX = "order:cart:";
|
||||
|
||||
/**
|
||||
* 订单防重提交令牌缓存键前缀
|
||||
*/
|
||||
String ORDER_TOKEN_PREFIX = "order:token:";
|
||||
|
||||
/**
|
||||
* 订单锁缓存键前缀
|
||||
*/
|
||||
String ORDER_LOCK_PREFIX = "order:lock";
|
||||
|
||||
}
|
@ -8,11 +8,12 @@ import com.youlai.mall.oms.model.dto.OrderDTO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.service.OrderItemService;
|
||||
import com.youlai.mall.oms.model.vo.OmsOrderPageVO;
|
||||
import com.youlai.mall.oms.service.admin.OmsOrderService;
|
||||
import com.youlai.mall.oms.service.app.OrderItemService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@ -24,12 +25,12 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 「管理端」订单控制层
|
||||
* 管理端-订单控制层
|
||||
*
|
||||
* @author huawei
|
||||
* @since 2020/12/30
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Tag(name = "「管理端」订单管理")
|
||||
@Tag(name = "「管理端」订单管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/orders")
|
||||
@RequiredArgsConstructor
|
||||
@ -41,15 +42,15 @@ public class OmsOrderController {
|
||||
|
||||
@Operation(summary ="订单分页列表")
|
||||
@GetMapping
|
||||
public PageResult listOrderPages(OrderPageQuery queryParams) {
|
||||
IPage<OmsOrder> result = orderService.listOrderPages(queryParams);
|
||||
return PageResult.success(result);
|
||||
public PageResult<OmsOrderPageVO> getOrderPage(OrderPageQuery queryParams) {
|
||||
IPage<OmsOrderPageVO> page = orderService.getOrderPage(queryParams);
|
||||
return PageResult.success(page);
|
||||
}
|
||||
|
||||
@Operation(summary= "订单详情")
|
||||
@Operation(summary = "订单详情")
|
||||
@GetMapping("/{orderId}")
|
||||
public Result getOrderDetail(
|
||||
@Parameter(name = "订单ID") @PathVariable Long orderId
|
||||
@Parameter(name ="订单ID") @PathVariable Long orderId
|
||||
) {
|
||||
OrderDTO orderDTO = new OrderDTO();
|
||||
// 订单
|
||||
@ -64,5 +65,4 @@ public class OmsOrderController {
|
||||
orderDTO.setOrder(order).setOrderItems(orderItems);
|
||||
return Result.success(orderDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package com.youlai.mall.oms.controller.app;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.security.util.SecurityUtils;
|
||||
import com.youlai.mall.oms.model.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.service.CartService;
|
||||
import com.youlai.mall.oms.service.app.CartService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@ -16,11 +16,10 @@ import java.util.List;
|
||||
* 「移动端」购物车接口
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
*/
|
||||
|
||||
@Tag(name = "「移动端」购物车接口")
|
||||
@Tag(name = "「移动端」购物车接口")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/carts")
|
||||
@RequiredArgsConstructor
|
||||
@ -28,28 +27,28 @@ public class CartController {
|
||||
|
||||
private final CartService cartService;
|
||||
|
||||
@Operation(summary= "查询购物车")
|
||||
@Operation(summary = "查询购物车")
|
||||
@GetMapping
|
||||
public <T> Result<T> getCart() {
|
||||
List<CartItemDTO> result = cartService.listCartItems(SecurityUtils.getMemberId());
|
||||
return Result.success((T) result);
|
||||
}
|
||||
|
||||
@Operation(summary= "删除购物车")
|
||||
@Operation(summary = "删除购物车")
|
||||
@DeleteMapping
|
||||
public <T> Result<T> deleteCart() {
|
||||
boolean result = cartService.deleteCart();
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "添加购物车商品")
|
||||
@Operation(summary = "添加购物车商品")
|
||||
@PostMapping
|
||||
public <T> Result<T> addCartItem(@RequestParam Long skuId) {
|
||||
cartService.addCartItem(skuId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary= "更新购物车商品")
|
||||
@Operation(summary = "更新购物车商品")
|
||||
@PutMapping("/skuId/{skuId}")
|
||||
public <T> Result<T> updateCartItem(
|
||||
@PathVariable Long skuId,
|
||||
@ -60,17 +59,17 @@ public class CartController {
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "删除购物车商品")
|
||||
@Operation(summary = "删除购物车商品")
|
||||
@DeleteMapping("/skuId/{skuId}")
|
||||
public <T> Result<T> removeCartItem(@PathVariable Long skuId) {
|
||||
boolean result = cartService.removeCartItem(skuId);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "全选/全不选购物车商品")
|
||||
@Operation(summary = "全选/全不选购物车商品")
|
||||
@PatchMapping("/_check")
|
||||
public <T> Result<T> check(
|
||||
@Parameter(name = "全选/全不选") boolean checked
|
||||
@Parameter(name ="全选/全不选") boolean checked
|
||||
) {
|
||||
boolean result = cartService.checkAll(checked);
|
||||
return Result.judge(result);
|
||||
|
@ -3,74 +3,68 @@ package com.youlai.mall.oms.controller.app;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.youlai.common.result.PageResult;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.form.OrderPaymentForm;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OrderConfirmVO;
|
||||
import com.youlai.mall.oms.model.vo.OrderSubmitResultVO;
|
||||
import com.youlai.mall.oms.service.OrderService;
|
||||
import com.youlai.mall.oms.model.vo.OrderPageVO;
|
||||
import com.youlai.mall.oms.service.app.OrderService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 「移动端」订单控制层
|
||||
* APP-订单控制层
|
||||
*
|
||||
* @author huawei
|
||||
* @since 2020/12/30
|
||||
*/
|
||||
@Tag(name = "「移动端」订单接口")
|
||||
@Tag(name = "APP-订单接口")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/orders")
|
||||
@RequiredArgsConstructor
|
||||
public class OrderController {
|
||||
|
||||
final OrderService orderService;
|
||||
private final OrderService orderService;
|
||||
|
||||
@Operation(summary ="分页列表")
|
||||
@Operation(summary ="订单分页列表")
|
||||
@GetMapping
|
||||
public PageResult listOrderPages(OrderPageQuery queryParams) {
|
||||
IPage<OmsOrder> result = orderService.listOrderPages(queryParams);
|
||||
public PageResult<OrderPageVO> getOrderPage(OrderPageQuery queryParams) {
|
||||
IPage<OrderPageVO> result = orderService.getOrderPage(queryParams);
|
||||
return PageResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单确认 → 进入创建订单页面
|
||||
* <p>
|
||||
* 获取购买商品明细、用户默认收货地址、防重提交唯一token
|
||||
* 进入订单创建页面有两个入口,1:立即购买;2:购物车结算
|
||||
*
|
||||
* @param skuId 直接购买必填,购物车结算不填
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary ="订单确认")
|
||||
@PostMapping("/_confirm")
|
||||
public Result<OrderConfirmVO> confirmOrder(@RequestParam(required = false) Long skuId) {
|
||||
@Operation(summary = "订单确认", description = "进入订单确认页面有两个入口,1:立即购买;2:购物车结算")
|
||||
@PostMapping("/confirm")
|
||||
public Result<OrderConfirmVO> confirmOrder(
|
||||
@Parameter(name ="立即购买必填,购物车结算不填") @RequestParam(required = false) Long skuId
|
||||
) {
|
||||
OrderConfirmVO result = orderService.confirmOrder(skuId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary ="订单提交")
|
||||
@PostMapping("/_submit")
|
||||
public Result submitOrder(@RequestBody @Validated OrderSubmitForm orderSubmitForm) {
|
||||
OrderSubmitResultVO result = orderService.submitOrder(orderSubmitForm);
|
||||
return Result.success(result);
|
||||
@PostMapping("/submit")
|
||||
public Result<String> submitOrder(@Validated @RequestBody OrderSubmitForm submitForm) {
|
||||
String orderSn = orderService.submitOrder(submitForm);
|
||||
return Result.success(orderSn);
|
||||
}
|
||||
|
||||
@Operation(summary ="订单支付")
|
||||
@PostMapping("/{orderId}/_pay")
|
||||
public Result payOrder(@PathVariable Long orderId) {
|
||||
boolean result = orderService.payOrder(orderId);
|
||||
@PostMapping("/payment")
|
||||
public Result payOrder(@Validated @RequestBody OrderPaymentForm paymentForm) {
|
||||
boolean result = orderService.payOrder(paymentForm);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary ="订单删除")
|
||||
@DeleteMapping("/{orderId}")
|
||||
public Result deleteOrder(@PathVariable Long orderId) {
|
||||
boolean result = orderService.deleteOrder(orderId);
|
||||
return Result.judge(result);
|
||||
boolean deleted = orderService.deleteOrder(orderId);
|
||||
return Result.judge(deleted);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
|
||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.youlai.mall.oms.model.vo.WxPayResponseVO;
|
||||
import com.youlai.mall.oms.service.OrderService;
|
||||
import com.youlai.mall.oms.service.app.OrderService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -0,0 +1,24 @@
|
||||
|
||||
package com.youlai.mall.oms.converter;
|
||||
|
||||
import com.youlai.mall.oms.model.dto.CartItemDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
|
||||
/**
|
||||
* 购物车对象转化器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface CartConverter {
|
||||
|
||||
@Mappings({
|
||||
@Mapping(target = "skuId", source = "id"),
|
||||
})
|
||||
CartItemDTO sku2CartItem(SkuInfoDTO skuInfo);
|
||||
|
||||
}
|
@ -1,25 +1,81 @@
|
||||
package com.youlai.mall.oms.converter;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.mall.oms.model.bo.OrderBO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.model.vo.OmsOrderPageVO;
|
||||
import com.youlai.mall.oms.model.vo.OrderPageVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
|
||||
|
||||
/**
|
||||
* 订单转化器
|
||||
* 订单对象转化器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/21
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface OrderConverter {
|
||||
|
||||
@Mappings({
|
||||
@Mapping(target = "orderSn", source = "orderToken"),
|
||||
@Mapping(target = "totalQuantity", expression = "java(orderSubmitForm.getOrderItems().stream().map(OrderItemDTO::getCount).reduce(0, Integer::sum))"),
|
||||
@Mapping(target = "totalAmount", expression = "java(orderSubmitForm.getOrderItems().stream().map(item -> item.getPrice() * item.getCount()).reduce(0L, Long::sum))"),
|
||||
@Mapping(target = "totalQuantity",
|
||||
expression = "java(orderSubmitForm.getOrderItems().stream().map(OrderSubmitForm.OrderItem::getQuantity).reduce(0, Integer::sum))"),
|
||||
@Mapping(target = "totalAmount",
|
||||
expression = "java(orderSubmitForm.getOrderItems().stream().map(item -> item.getPrice() * item.getQuantity()).reduce(0L, Long::sum))"),
|
||||
@Mapping(target = "source", expression = "java(orderSubmitForm.getOrderSource().getValue())"),
|
||||
})
|
||||
OmsOrder submitForm2Entity(OrderSubmitForm orderSubmitForm);
|
||||
OmsOrder form2Entity(OrderSubmitForm orderSubmitForm);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(
|
||||
target = "paymentMethodLabel",
|
||||
expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getPaymentMethod(), com.youlai.mall.oms.enums.PaymentMethodEnum.class))"
|
||||
),
|
||||
@Mapping(
|
||||
target = "sourceLabel",
|
||||
expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getSource(), com.youlai.mall.oms.enums.OrderSourceEnum.class))"
|
||||
),
|
||||
@Mapping(
|
||||
target = "statusLabel",
|
||||
expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getStatus(), com.youlai.mall.oms.enums.OrderStatusEnum.class))"
|
||||
),
|
||||
@Mapping(
|
||||
target = "orderItems",
|
||||
source = "orderItems"
|
||||
)
|
||||
})
|
||||
OmsOrderPageVO toVoPage(OrderBO bo);
|
||||
|
||||
Page<OmsOrderPageVO> toVoPage(Page<OrderBO> boPage);
|
||||
|
||||
OmsOrderPageVO.OrderItem toVoPageOrderItem(OrderBO.OrderItem orderItem);
|
||||
|
||||
|
||||
@Mappings({
|
||||
@Mapping(
|
||||
target = "paymentMethodLabel",
|
||||
expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getPaymentMethod(), com.youlai.mall.oms.enums.PaymentMethodEnum.class))"
|
||||
),
|
||||
@Mapping(
|
||||
target = "sourceLabel",
|
||||
expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getSource(), com.youlai.mall.oms.enums.OrderSourceEnum.class))"
|
||||
),
|
||||
@Mapping(
|
||||
target = "statusLabel",
|
||||
expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getStatus(), com.youlai.mall.oms.enums.OrderStatusEnum.class))"
|
||||
),
|
||||
@Mapping(
|
||||
target = "orderItems",
|
||||
source = "orderItems"
|
||||
)
|
||||
})
|
||||
OrderPageVO toVoPageForApp(OrderBO bo);
|
||||
|
||||
Page<OrderPageVO> toVoPageForApp(Page<OrderBO> boPage);
|
||||
|
||||
OrderPageVO.OrderItem toVoPageOrderItemForApp(OrderBO.OrderItem orderItem);
|
||||
}
|
@ -1,40 +1,29 @@
|
||||
|
||||
package com.youlai.mall.oms.converter;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.youlai.mall.oms.model.dto.OrderItemDTO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 订单对象转化器
|
||||
* 订单商品明细对象转化器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/21
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface OrderItemConverter {
|
||||
|
||||
@Mappings({
|
||||
@Mapping(target = "totalAmount", expression = "java(dto.getPrice() * dto.getCount())"),
|
||||
@Mapping(target = "orderId", source = "orderId"),
|
||||
@Mapping(target = "totalAmount", expression = "java(item.getPrice() * item.getQuantity())"),
|
||||
})
|
||||
OmsOrderItem dto2Entity(Long orderId, OrderItemDTO dto);
|
||||
OmsOrderItem item2Entity(OrderSubmitForm.OrderItem item);
|
||||
|
||||
List<OmsOrderItem> item2Entity(List<OrderSubmitForm.OrderItem> list);
|
||||
|
||||
default List<OmsOrderItem> dto2Entity(Long orderId, List<OrderItemDTO> list) {
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
List<OmsOrderItem> entities = list.stream().map(dto -> dto2Entity(orderId, dto))
|
||||
.collect(Collectors.toList());
|
||||
return entities;
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
@ -4,20 +4,19 @@ import com.youlai.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 订单来源类型枚举
|
||||
* 订单来源枚举
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2021/1/16
|
||||
*/
|
||||
|
||||
public enum OrderSourceTypeEnum implements IBaseEnum<Integer> {
|
||||
public enum OrderSourceEnum implements IBaseEnum<Integer> {
|
||||
|
||||
APP(1, "APP"), // APP订单
|
||||
PC(2, "PC"), // PC订单
|
||||
WEB(2, "WEB"), // 网页
|
||||
;
|
||||
|
||||
OrderSourceTypeEnum(Integer value, String label) {
|
||||
OrderSourceEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
@ -7,7 +7,7 @@ import lombok.Getter;
|
||||
* 订单状态枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/11/28
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum OrderStatusEnum implements IBaseEnum<Integer> {
|
||||
|
||||
@ -34,8 +34,7 @@ public enum OrderStatusEnum implements IBaseEnum<Integer> {
|
||||
/**
|
||||
* 售后中
|
||||
*/
|
||||
SERVICING(5, "售后中")
|
||||
;
|
||||
SERVICING(5, "售后中");
|
||||
|
||||
OrderStatusEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
|
@ -5,20 +5,19 @@ import com.youlai.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 订单支付类型枚举
|
||||
* 订单支付方式枚举
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2021/1/16
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum PayTypeEnum implements IBaseEnum<Integer> {
|
||||
public enum PaymentMethodEnum implements IBaseEnum<Integer> {
|
||||
|
||||
WX_JSAPI(1, "微信JSAPI支付"),
|
||||
ALIPAY(2, "支付宝支付"),
|
||||
BALANCE(3, "会员余额支付"),
|
||||
WX_APP(4, "微信APP支付");
|
||||
|
||||
PayTypeEnum(int value, String label) {
|
||||
PaymentMethodEnum(int value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.youlai.mall.oms.listener;
|
||||
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.youlai.mall.oms.service.app.OrderService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 订单超时未支付取消
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.0.0
|
||||
*/
|
||||
//@Component // 注解绑定死信队列无效暂不使用
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class OmsCloseListener {
|
||||
private final OrderService orderService;
|
||||
|
||||
// 延迟队列
|
||||
private static final String ORDER_CLOSE_DELAY_QUEUE = "order.close.delay.queue";
|
||||
private static final String ORDER_EXCHANGE = "order.exchange";
|
||||
private static final String ORDER_CLOSE_DELAY_ROUTING_KEY = "order.close.delay";
|
||||
|
||||
// 关单队列
|
||||
private static final String ORDER_ClOSE_QUEUE = "order.close.queue";
|
||||
private static final String ORDER_DLX_EXCHANGE = "order.dlx.exchange";
|
||||
private static final String ORDER_ClOSE_ROUTING_KEY = "order.close";
|
||||
|
||||
/**
|
||||
* 延迟队列
|
||||
* <p>
|
||||
* 超过 x-message-ttl 设定时间未被消费转发到死信交换机
|
||||
*/
|
||||
@RabbitListener(bindings =
|
||||
{
|
||||
@QueueBinding(
|
||||
value = @Queue(value = ORDER_CLOSE_DELAY_QUEUE,
|
||||
arguments =
|
||||
{
|
||||
@Argument(name = "x-dead-letter-exchange", value = ORDER_DLX_EXCHANGE),
|
||||
@Argument(name = "x-dead-letter-routing-key", value = ORDER_ClOSE_ROUTING_KEY),
|
||||
@Argument(name = "x-message-ttl", value = "10000", type = "java.lang.Long") // 超时10s
|
||||
}),
|
||||
exchange = @Exchange(value = ORDER_EXCHANGE),
|
||||
key = {ORDER_CLOSE_DELAY_ROUTING_KEY}
|
||||
)
|
||||
}, ackMode = "MANUAL" // 手动ACK
|
||||
)
|
||||
public void handleOrderCloseDelay(String orderSn, Message message, Channel channel) throws IOException {
|
||||
log.info("订单({})延时队列,10s内如果未支付将路由到关单队列", orderSn);
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag();
|
||||
/**
|
||||
* @param deliveryTag 消息序号
|
||||
* @param multiple 是否批量处理(true:批量拒绝所有小于deliveryTag的消息;false:只处理当前消息)
|
||||
* @param requeue 拒绝是否重新入队列 (true:消息重新入队;false:禁止消息重新入队)
|
||||
*/
|
||||
//channel.basicReject(deliveryTag, false); // 等于 channel.basicReject(deliveryTag, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关单队列
|
||||
*/
|
||||
@RabbitListener(bindings = {
|
||||
@QueueBinding(
|
||||
value = @Queue(value = ORDER_ClOSE_QUEUE, durable = "true"),
|
||||
exchange = @Exchange(value = ORDER_DLX_EXCHANGE),
|
||||
key = {ORDER_ClOSE_ROUTING_KEY}
|
||||
)
|
||||
}, ackMode = "MANUAL" // 手动ACK
|
||||
)
|
||||
@RabbitListener(queues = "order.close.queue")
|
||||
public void handleOrderClose(String orderSn, Message message, Channel channel) throws IOException {
|
||||
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag(); // 消息序号
|
||||
|
||||
log.info("订单({})超时未支付,系统自动关闭订单", orderSn);
|
||||
try {
|
||||
orderService.closeOrder(orderSn);
|
||||
channel.basicAck(deliveryTag, false);
|
||||
} catch (Exception e) {
|
||||
|
||||
// TODO 关单失败,入定时任务表
|
||||
channel.basicReject(deliveryTag, false);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +1,54 @@
|
||||
package com.youlai.mall.oms.listener;
|
||||
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.youlai.mall.oms.service.OrderService;
|
||||
import com.youlai.mall.oms.service.app.OrderService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.*;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 订单超时未支付取消
|
||||
* 订单超时未支付系统自动取消监听器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/19
|
||||
*/
|
||||
//@Component
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class OrderCloseListener {
|
||||
private final OrderService orderService;
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
// 延迟队列
|
||||
private static final String ORDER_CLOSE_DELAY_QUEUE = "order.close.delay.queue";
|
||||
private static final String ORDER_EXCHANGE = "order.exchange";
|
||||
private static final String ORDER_CLOSE_DELAY_ROUTING_KEY = "order.close.delay.routing.key";
|
||||
|
||||
// 关单队列
|
||||
private static final String ORDER_ClOSE_QUEUE = "order.close.queue";
|
||||
private static final String ORDER_DLX_EXCHANGE = "order.dlx.exchange";
|
||||
private static final String ORDER_ClOSE_ROUTING_KEY = "order.close.routing.key";
|
||||
|
||||
/**
|
||||
* 延迟队列
|
||||
* <p>
|
||||
* 超过 x-message-ttl 设定时间未被消费转发到死信交换机
|
||||
*/
|
||||
@RabbitListener(bindings =
|
||||
{
|
||||
@QueueBinding(
|
||||
value = @Queue(value = ORDER_CLOSE_DELAY_QUEUE,
|
||||
arguments =
|
||||
{
|
||||
@Argument(name = "x-dead-letter-exchange", value = ORDER_DLX_EXCHANGE),
|
||||
@Argument(name = "x-dead-letter-routing-key", value = ORDER_ClOSE_ROUTING_KEY),
|
||||
@Argument(name = "x-message-ttl", value = "5000", type = "java.lang.Long") // 超时10s
|
||||
}),
|
||||
exchange = @Exchange(value = ORDER_EXCHANGE),
|
||||
key = {ORDER_CLOSE_DELAY_ROUTING_KEY}
|
||||
)
|
||||
}, ackMode = "MANUAL" // 手动ACK
|
||||
)
|
||||
public void handleOrderCloseDelay(String orderSn, Message message, Channel channel) throws IOException {
|
||||
log.info("订单【{}】延时队列,10s内如果未支付将路由到关单队列", orderSn);
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag();
|
||||
/**
|
||||
* @param deliveryTag 消息序号
|
||||
* @param multiple 是否批量处理(true:批量拒绝所有小于deliveryTag的消息;false:只处理当前消息)
|
||||
* @param requeue 拒绝是否重新入队列 (true:消息重新入队;false:禁止消息重新入队)
|
||||
*/
|
||||
//channel.basicReject(deliveryTag, false); // 等于 channel.basicReject(deliveryTag, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关单队列
|
||||
*/
|
||||
@RabbitListener(bindings = {
|
||||
@QueueBinding(
|
||||
value = @Queue(value = ORDER_ClOSE_QUEUE, durable = "true"),
|
||||
exchange = @Exchange(value = ORDER_DLX_EXCHANGE),
|
||||
key = {ORDER_ClOSE_ROUTING_KEY}
|
||||
)
|
||||
}, ackMode = "MANUAL" // 手动ACK
|
||||
)
|
||||
@RabbitListener(queues = "order.close.queue")
|
||||
public void handleOrderClose(String orderSn, Message message, Channel channel) throws IOException {
|
||||
public void closeOrder(String orderSn, Message message, Channel channel) {
|
||||
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag(); // 消息序号
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag(); // 消息序号(消息队列中的位置)
|
||||
|
||||
log.info("订单 【{}】 超时未支付,系统自动关闭订单", orderSn);
|
||||
log.info("订单({})超时未支付,系统自动关闭订单", orderSn);
|
||||
try {
|
||||
orderService.closeOrder(orderSn);
|
||||
channel.basicAck(deliveryTag, false);
|
||||
boolean closeOrderResult = orderService.closeOrder(orderSn);
|
||||
log.info("关单结果:{}", closeOrderResult);
|
||||
if (closeOrderResult) {
|
||||
// 关单成功:释放库存
|
||||
rabbitTemplate.convertAndSend("stock.exchange", "stock.unlock", orderSn);
|
||||
} else {
|
||||
// 关单失败:订单已被关闭,手动ACK确认并从队列移除消息
|
||||
channel.basicAck(deliveryTag, false); // false: 不批量确认,仅确认当前单个消息
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 关单异常:拒绝消息并重新入队
|
||||
try {
|
||||
channel.basicReject(deliveryTag, true); // true: 重新放回队列
|
||||
// channel.basicReject(deliveryTag, false); // false: 直接丢弃消息 (TODO 定时任务补偿)
|
||||
} catch (IOException ex) {
|
||||
log.error("订单({})关闭失败,原因:{}", orderSn, ex.getMessage());
|
||||
}
|
||||
|
||||
// TODO 关单失败,入定时任务表
|
||||
channel.basicReject(deliveryTag, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
package com.youlai.mall.oms.mapper;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderDelivery;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderDelivery;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单物流记录表
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderDeliveryMapper extends BaseMapper<OmsOrderDelivery> {
|
||||
|
@ -1,11 +1,15 @@
|
||||
package com.youlai.mall.oms.mapper;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderLog;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单操作历史记录
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderLogMapper extends BaseMapper<OmsOrderLog> {
|
||||
|
@ -2,14 +2,16 @@ package com.youlai.mall.oms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.mall.oms.model.bo.OrderBO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单表
|
||||
* 订单数据访问层
|
||||
*
|
||||
* @author huawei
|
||||
* @since 2020-12-30 22:31:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderMapper extends BaseMapper<OmsOrder> {
|
||||
@ -21,5 +23,5 @@ public interface OrderMapper extends BaseMapper<OmsOrder> {
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
List<OmsOrder> listOrderPages(Page<OmsOrder> page, OrderPageQuery queryParams);
|
||||
Page<OrderBO> getOrderPage(Page<OrderBO> page, OrderPageQuery queryParams);
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
package com.youlai.mall.oms.mapper;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderPay;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderPay;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 支付信息表
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderPayMapper extends BaseMapper<OmsOrderPay> {
|
||||
|
@ -1,11 +1,15 @@
|
||||
package com.youlai.mall.oms.mapper;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderSetting;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderSetting;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单配置信息
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderSettingMapper extends BaseMapper<OmsOrderSetting> {
|
||||
|
@ -0,0 +1,123 @@
|
||||
package com.youlai.mall.oms.model.bo;
|
||||
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import com.youlai.mall.oms.enums.OrderSourceEnum;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.PaymentMethodEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单业务对象
|
||||
*
|
||||
* @author huawei
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class OrderBO extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderSn;
|
||||
/**
|
||||
* 订单总额(分)
|
||||
*/
|
||||
private Long totalAmount;
|
||||
/**
|
||||
* 商品总数
|
||||
*/
|
||||
private Integer totalQuantity;
|
||||
|
||||
/**
|
||||
* 订单来源 {@link OrderSourceEnum}
|
||||
*/
|
||||
private Integer source;
|
||||
|
||||
/**
|
||||
* 订单状态 {@link OrderStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 应付总额(分)
|
||||
*/
|
||||
private Long paymentAmount;
|
||||
|
||||
/**
|
||||
* 支付方式 {@link PaymentMethodEnum}
|
||||
*/
|
||||
private Integer paymentMethod;
|
||||
|
||||
/**
|
||||
* 订单创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 订单商品明细列表
|
||||
*/
|
||||
private List<OrderItem> orderItems;
|
||||
|
||||
@Data
|
||||
public static class OrderItem{
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 规格ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* SKU编号
|
||||
*/
|
||||
private String skuSn;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String skuName;
|
||||
|
||||
/**
|
||||
* 商品sku图片
|
||||
*/
|
||||
private String picUrl;
|
||||
|
||||
/**
|
||||
* 商品单价(单位:分)
|
||||
*/
|
||||
private Long price;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 商品总金额(单位:分)(单价*数量)
|
||||
*/
|
||||
private Long totalAmount;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.youlai.mall.oms.model.dto;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
@ -2,18 +2,11 @@ package com.youlai.mall.oms.model.dto;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
import com.youlai.mall.ums.dto.MemberRegisterDto;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @desc
|
||||
* @email huawei_code@163.com
|
||||
* @since 2021/1/19
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OrderDTO {
|
||||
@ -22,6 +15,4 @@ public class OrderDTO {
|
||||
|
||||
private List<OmsOrderItem> orderItems;
|
||||
|
||||
private MemberRegisterDto member;
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package com.youlai.mall.oms.model.dto;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 订单商品
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/21
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Data
|
||||
public class OrderItemDTO {
|
||||
@ -44,5 +44,5 @@ public class OrderItemDTO {
|
||||
/**
|
||||
* 订单商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
private Integer quantity;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -15,16 +15,13 @@ import java.util.List;
|
||||
* 订单详情表
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OmsOrder extends BaseEntity {
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
/**
|
||||
@ -42,7 +39,7 @@ public class OmsOrder extends BaseEntity {
|
||||
/**
|
||||
* 订单来源(0-PC订单;1-app订单)
|
||||
*/
|
||||
private Integer sourceType;
|
||||
private Integer source;
|
||||
|
||||
/**
|
||||
* 订单状态(1-待付款;2-待发货;3-已发货;4-已完成;5-已关闭;6-已取消;)
|
||||
@ -71,15 +68,15 @@ public class OmsOrder extends BaseEntity {
|
||||
/**
|
||||
* 应付总额(分)
|
||||
*/
|
||||
private Long payAmount;
|
||||
private Long paymentAmount;
|
||||
/**
|
||||
* 支付时间
|
||||
*/
|
||||
private Date payTime;
|
||||
private Date paymentTime;
|
||||
/**
|
||||
* 支付方式【1->微信jsapi;2->支付宝;3->余额; 4->微信app;】
|
||||
* 支付方式【1->微信jsapi;2->支付宝;3->余额;4->微信app;】
|
||||
*/
|
||||
private Integer payType;
|
||||
private Integer paymentMethod;
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
|
@ -13,7 +13,7 @@ import java.util.Date;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
|
@ -4,17 +4,16 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 订单明细表
|
||||
* 订单商品明细
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @since 2020-12-30
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OmsOrderItem extends BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
@ -36,7 +35,7 @@ public class OmsOrderItem extends BaseEntity {
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* SKU编号
|
||||
* SKU 编号
|
||||
*/
|
||||
private String skuSn;
|
||||
|
||||
@ -58,7 +57,7 @@ public class OmsOrderItem extends BaseEntity {
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 商品总金额(单位:分)
|
||||
|
@ -10,7 +10,7 @@ import lombok.Data;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Data
|
||||
public class OmsOrderLog extends BaseEntity {
|
||||
|
@ -13,7 +13,7 @@ import java.util.Date;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
|
@ -10,7 +10,7 @@ import lombok.Data;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Data
|
||||
public class OmsOrderSetting extends BaseEntity {
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.youlai.mall.oms.model.form;
|
||||
|
||||
import com.youlai.mall.oms.enums.PaymentMethodEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 订单支付表单对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Data
|
||||
@Schema(description ="订单支付表单对象")
|
||||
public class OrderPaymentForm {
|
||||
|
||||
@Schema(description="订单编号")
|
||||
private String orderSn;
|
||||
|
||||
@Schema(description="小程序 AppId")
|
||||
String appId;
|
||||
|
||||
@Schema(description="支付方式")
|
||||
private PaymentMethodEnum paymentMethod;
|
||||
|
||||
}
|
@ -1,55 +1,99 @@
|
||||
package com.youlai.mall.oms.model.form;
|
||||
|
||||
import com.youlai.mall.oms.enums.OrderSourceTypeEnum;
|
||||
import com.youlai.mall.oms.model.dto.OrderItemDTO;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import com.youlai.mall.oms.enums.OrderSourceEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单提交表单对象
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2021/1/16
|
||||
* @author haoxr
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class OrderSubmitForm {
|
||||
|
||||
|
||||
/**
|
||||
* 订单来源
|
||||
*
|
||||
* @see OrderSourceTypeEnum
|
||||
*/
|
||||
@Schema(description="订单来源")
|
||||
private Integer sourceType;
|
||||
|
||||
@Schema(description="提交订单确认页面签发的令牌(防止订单重复提交,订单提交成功转为订单编号)")
|
||||
@Schema(description="订单确认页面签发的令牌(防止重复提交)")
|
||||
@NotBlank(message = "订单令牌不能为空")
|
||||
private String orderToken;
|
||||
|
||||
@Schema(description="订单总金额-用于验价(单位:分)")
|
||||
private Long totalAmount;
|
||||
@Schema(description="订单来源")
|
||||
@NotNull(message = "订单来源不能为空")
|
||||
private OrderSourceEnum orderSource;
|
||||
|
||||
@Schema(description="支付金额(单位:分)")
|
||||
private Long payAmount;
|
||||
@Schema(description="订单商品明细")
|
||||
@NotEmpty(message = "订单商品不能为空")
|
||||
private List<OrderItem> orderItems;
|
||||
|
||||
@Schema(description="订单的商品明细")
|
||||
private List<OrderItemDTO> orderItems;
|
||||
@Schema(description="应付金额(单位:分)")
|
||||
@NotNull(message = "应付金额不能为空")
|
||||
private Long paymentAmount;
|
||||
|
||||
@Schema(description="收获地址")
|
||||
@NotNull(message = "收货地址不能为空")
|
||||
private ShippingAddress shippingAddress;
|
||||
|
||||
@Schema(description="订单备注")
|
||||
@Size(max = 500, message = "订单备注长度不能超过500")
|
||||
private String remark;
|
||||
|
||||
@Schema(description="优惠券ID")
|
||||
private String couponId;
|
||||
@Schema(description ="收获地址")
|
||||
@Data
|
||||
public static class ShippingAddress {
|
||||
|
||||
@Schema(description="收货人姓名")
|
||||
private String consigneeName;
|
||||
|
||||
@Schema(description="收货人手机号")
|
||||
private String consigneeMobile;
|
||||
|
||||
@Schema(description="省份")
|
||||
private String province;
|
||||
|
||||
@Schema(description="城市")
|
||||
private String city;
|
||||
|
||||
@Schema(description="区域")
|
||||
private String district;
|
||||
|
||||
@Schema(description="详细地址")
|
||||
private String detailAddress;
|
||||
}
|
||||
|
||||
@Schema(description ="订单商品")
|
||||
@Data
|
||||
public static class OrderItem {
|
||||
|
||||
@Schema(description = "SKU ID")
|
||||
private Long skuId;
|
||||
|
||||
@Schema(description = "SKU 编号")
|
||||
private String skuSn;
|
||||
|
||||
@Schema(description = "SKU 名称")
|
||||
private String skuName;
|
||||
|
||||
@Schema(description = "商品图片URL")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description = "商品价格(单位:分)")
|
||||
private Long price;
|
||||
|
||||
@Schema(description = "商品名称")
|
||||
private String spuName;
|
||||
|
||||
@Schema(description = "商品数量")
|
||||
private Integer quantity;
|
||||
}
|
||||
|
||||
@Schema(description="收获地址")
|
||||
private MemberAddressDTO deliveryAddress;
|
||||
|
||||
}
|
||||
|
@ -1,31 +1,49 @@
|
||||
package com.youlai.mall.oms.model.query;
|
||||
|
||||
import com.youlai.common.base.BasePageQuery;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单分页查询对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/1 19:14
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description ="订单分页查询对象")
|
||||
@Data
|
||||
@Schema(description = "订单分页查询对象")
|
||||
public class OrderPageQuery extends BasePageQuery {
|
||||
|
||||
/**
|
||||
* 关键字(订单编号/商品名称/会员姓名/会员手机号)
|
||||
*/
|
||||
@Schema(description="关键字(订单编号/商品名称/会员姓名/会员手机号)")
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
@Schema(description="订单状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description="会员ID")
|
||||
private Long memberId;
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@Schema(description = "开始时间(yyyy-MM-dd)",example = "2023-10-01")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd 00:00:00") // DateTimeFormat 用于将查询参数或表单参数转换为日期类型
|
||||
private Date beginDate;
|
||||
|
||||
@Schema(description="订单编号")
|
||||
private String orderSn;
|
||||
|
||||
@Schema(description = "开始时间(格式:yyyy-MM-dd)")
|
||||
private String beginDate;
|
||||
|
||||
@Schema(description = "截止时间(格式:yyyy-MM-dd)")
|
||||
private String endDate;
|
||||
/**
|
||||
* 截止时间
|
||||
*/
|
||||
@Schema(description = "截止时间(yyyy-MM-dd)",example = "2025-10-01")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd 23:59:59")
|
||||
private Date endDate;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
package com.youlai.mall.oms.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Admin-订单分页视图对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Schema(description ="Admin-订单分页视图对象")
|
||||
@Data
|
||||
public class OmsOrderPageVO {
|
||||
|
||||
@Schema(description="订单ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description="订单编号")
|
||||
private String orderSn;
|
||||
|
||||
@Schema(description="订单总金额(分)")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
@Schema(description="订单总金额(分)")
|
||||
private Long paymentAmount;
|
||||
|
||||
@Schema(description="支付方式标签")
|
||||
private String paymentMethodLabel;
|
||||
|
||||
@Schema(description="订单状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description="订单状态标签")
|
||||
private String statusLabel;
|
||||
|
||||
@Schema(description="商品总数")
|
||||
private Integer totalQuantity;
|
||||
|
||||
@Schema(description="订单创建时间")
|
||||
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description="订单来源标签")
|
||||
private String sourceLabel;
|
||||
|
||||
@Schema(description="订单备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description="订单商品集合")
|
||||
private List<OrderItem> orderItems;
|
||||
|
||||
@Schema(description ="订单商品明细")
|
||||
@Data
|
||||
public static class OrderItem {
|
||||
|
||||
@Schema(description="商品ID")
|
||||
private Long skuId;
|
||||
|
||||
@Schema(description="商品规格名称")
|
||||
private String skuName;
|
||||
|
||||
@Schema(description="图片地址")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description="商品价格")
|
||||
private Long price;
|
||||
|
||||
@Schema(description="商品数量")
|
||||
private Integer quantity;
|
||||
|
||||
@Schema(description="商品总金额(单位:分)")
|
||||
private Long totalAmount;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -2,22 +2,34 @@ package com.youlai.mall.oms.model.vo;
|
||||
|
||||
import com.youlai.mall.oms.model.dto.OrderItemDTO;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Schema(description = "订单确认视图层对象")
|
||||
/**
|
||||
* 订单确认响应对象
|
||||
*/
|
||||
@Schema(description ="订单确认响应对象")
|
||||
@Data
|
||||
public class OrderConfirmVO {
|
||||
|
||||
@Schema(description="订单token")
|
||||
/**
|
||||
* 订单防重提交令牌
|
||||
*/
|
||||
@Schema(description="订单防重提交令牌")
|
||||
private String orderToken;
|
||||
|
||||
@Schema(description="订单明细")
|
||||
/**
|
||||
* 订单商品
|
||||
*/
|
||||
@Schema(description="订单商品")
|
||||
private List<OrderItemDTO> orderItems;
|
||||
|
||||
/**
|
||||
* 会员收货地址列表
|
||||
*/
|
||||
@Schema(description="会员收获地址列表")
|
||||
private List<MemberAddressDTO> addresses;
|
||||
|
||||
|
@ -1,62 +1,83 @@
|
||||
package com.youlai.mall.oms.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单分页视图对象
|
||||
* App-订单分页视图对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/1 20:58
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Schema(description ="App-订单分页视图对象")
|
||||
@Data
|
||||
public class OrderPageVO {
|
||||
|
||||
@Schema(description="订单ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description="订单编号")
|
||||
private String orderSn;
|
||||
|
||||
private Long totalAmount;
|
||||
@Schema(description="订单总金额(分)")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
private Long payAmount;
|
||||
@Schema(description="订单总金额(分)")
|
||||
private Long paymentAmount;
|
||||
|
||||
private Integer payType;
|
||||
@Schema(description="支付方式标签")
|
||||
private String paymentMethodLabel;
|
||||
|
||||
@Schema(description="订单状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description="订单状态标签")
|
||||
private String statusLabel;
|
||||
|
||||
@Schema(description="商品总数")
|
||||
private Integer totalQuantity;
|
||||
|
||||
@Schema(description="订单创建时间")
|
||||
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private Long memberId;
|
||||
@Schema(description="订单来源标签")
|
||||
private String sourceLabel;
|
||||
|
||||
private Integer sourceType;
|
||||
@Schema(description="订单备注")
|
||||
private String remark;
|
||||
|
||||
private List<OrderItem> orderItems;
|
||||
@Schema(description="订单商品集合")
|
||||
private List<OmsOrderPageVO.OrderItem> orderItems;
|
||||
|
||||
@Schema(description ="订单商品明细")
|
||||
@Data
|
||||
public static class OrderItem {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description="商品ID")
|
||||
private Long skuId;
|
||||
|
||||
@Schema(description="商品规格名称")
|
||||
private String skuName;
|
||||
|
||||
@Schema(description="图片地址")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description="商品价格")
|
||||
private Long price;
|
||||
|
||||
private Integer count;
|
||||
@Schema(description="商品数量")
|
||||
private Integer quantity;
|
||||
|
||||
@Schema(description="商品总金额(单位:分)")
|
||||
private Long totalAmount;
|
||||
|
||||
private String spuName;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package com.youlai.mall.oms.model.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 订单提交结果
|
||||
*
|
||||
* @author huawei
|
||||
* @since 2021/1/21
|
||||
*/
|
||||
@Schema(description = "订单提交结果")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class OrderSubmitResultVO {
|
||||
|
||||
@Schema(description="订单ID")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description="订单编号,进入支付页面显示")
|
||||
private String orderSn;
|
||||
|
||||
}
|
@ -2,24 +2,24 @@ package com.youlai.mall.oms.service.admin;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.oms.dto.SeataOrderDTO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OmsOrderPageVO;
|
||||
|
||||
/**
|
||||
* 「管理端」订单业务接口
|
||||
* Admin-订单业务接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2020/12/30
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public interface OmsOrderService extends IService<OmsOrder> {
|
||||
/**
|
||||
* 订单分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
* @param queryParams {@link OrderPageQuery}
|
||||
* @return
|
||||
*/
|
||||
IPage<OmsOrder> listOrderPages(OrderPageQuery queryParams);
|
||||
IPage<OmsOrderPageVO> getOrderPage(OrderPageQuery queryParams);
|
||||
|
||||
|
||||
}
|
||||
|
@ -3,37 +3,42 @@ package com.youlai.mall.oms.service.admin.impl;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.oms.converter.OrderConverter;
|
||||
import com.youlai.mall.oms.mapper.OrderMapper;
|
||||
import com.youlai.mall.oms.model.bo.OrderBO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OmsOrderPageVO;
|
||||
import com.youlai.mall.oms.service.admin.OmsOrderService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 「管理端」订单业务实现类
|
||||
* Admin-订单业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/12
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OmsOrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> implements OmsOrderService {
|
||||
|
||||
private final OrderConverter orderConverter;
|
||||
|
||||
|
||||
/**
|
||||
* 订单分页列表
|
||||
* Admin-订单分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
* @return
|
||||
* @param queryParams {@link OrderPageQuery}
|
||||
* @return {@link OmsOrderPageVO}
|
||||
*/
|
||||
@Override
|
||||
public IPage<OmsOrder> listOrderPages(OrderPageQuery queryParams) {
|
||||
Page<OmsOrder> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<OmsOrder> list = this.baseMapper.listOrderPages(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
public IPage<OmsOrderPageVO> getOrderPage(OrderPageQuery queryParams) {
|
||||
Page<OrderBO> boPage = this.baseMapper.getOrderPage(
|
||||
new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
|
||||
queryParams);
|
||||
|
||||
return orderConverter.toVoPage(boPage);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
package com.youlai.mall.oms.service.app;
|
||||
|
||||
import com.youlai.mall.oms.model.dto.CartItemDTO;
|
||||
|
||||
@ -8,7 +8,7 @@ import java.util.List;
|
||||
* 购物车业务接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/11/13
|
||||
* @date 2022/11/13
|
||||
*/
|
||||
public interface CartService {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
package com.youlai.mall.oms.service.app;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderDelivery;
|
||||
|
||||
/**
|
||||
@ -9,7 +8,7 @@ import com.youlai.mall.oms.model.entity.OmsOrderDelivery;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
public interface OrderDeliveryService extends IService<OmsOrderDelivery> {
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
package com.youlai.mall.oms.service.app;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
@ -9,7 +9,7 @@ import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
public interface OrderItemService extends IService<OmsOrderItem> {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
package com.youlai.mall.oms.service.app;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderLog;
|
||||
|
||||
/**
|
||||
@ -9,7 +8,7 @@ import com.youlai.mall.oms.model.entity.OmsOrderLog;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
public interface OrderLogService extends IService<OmsOrderLog> {
|
||||
|
@ -1,21 +1,20 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
package com.youlai.mall.oms.service.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.form.OrderPaymentForm;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OrderConfirmVO;
|
||||
import com.youlai.mall.oms.model.vo.OrderSubmitResultVO;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.model.vo.OrderPageVO;
|
||||
|
||||
/**
|
||||
* 订单业务接口
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
*/
|
||||
public interface OrderService extends IService<OmsOrder> {
|
||||
@ -27,19 +26,22 @@ public interface OrderService extends IService<OmsOrder> {
|
||||
* 进入订单创建页面有两个入口,1:立即购买;2:购物车结算
|
||||
*
|
||||
* @param skuId 直接购买必填,购物车结算不填
|
||||
* @return
|
||||
* @return {@link OrderConfirmVO}
|
||||
*/
|
||||
OrderConfirmVO confirmOrder(Long skuId);
|
||||
|
||||
/**
|
||||
* 订单提交
|
||||
*
|
||||
* @param orderSubmitForm {@link OrderSubmitForm}
|
||||
* @return 订单编号
|
||||
*/
|
||||
OrderSubmitResultVO submitOrder(OrderSubmitForm orderSubmitForm);
|
||||
String submitOrder(OrderSubmitForm orderSubmitForm);
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
*/
|
||||
boolean payOrder(Long orderId);
|
||||
<T> T payOrder(OrderPaymentForm paymentForm);
|
||||
|
||||
/**
|
||||
* 系统关闭订单
|
||||
@ -72,10 +74,10 @@ public interface OrderService extends IService<OmsOrder> {
|
||||
/**
|
||||
* 订单分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
* @return
|
||||
* @param queryParams 订单分页查询参数
|
||||
* @return {@link OrderPageVO}
|
||||
*/
|
||||
IPage<OmsOrder> listOrderPages(OrderPageQuery queryParams);
|
||||
IPage<OrderPageVO> getOrderPage(OrderPageQuery queryParams);
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
package com.youlai.mall.oms.service.app;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderSetting;
|
||||
|
||||
/**
|
||||
@ -9,7 +8,7 @@ import com.youlai.mall.oms.model.entity.OmsOrderSetting;
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @since 2020-12-30 22:31:10
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
public interface OrderSettingService extends IService<OmsOrderSetting> {
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
package com.youlai.mall.oms.service.app.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.common.security.util.SecurityUtils;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.constant.OrderConstants;
|
||||
import com.youlai.mall.oms.constant.OrderConstants;
|
||||
import com.youlai.mall.oms.converter.CartConverter;
|
||||
import com.youlai.mall.oms.model.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.service.CartService;
|
||||
import com.youlai.mall.oms.service.app.CartService;
|
||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.BoundHashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@ -18,7 +17,6 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
||||
/**
|
||||
@ -26,18 +24,19 @@ import java.util.concurrent.CompletableFuture;
|
||||
* <p>
|
||||
* 核心技术:BoundHashOperations
|
||||
* 数据格式:
|
||||
* -- key <----> 购物车
|
||||
* -- hKey:value <----> 商品1
|
||||
* -- hKey:value <----> 商品2
|
||||
* -- hKey:value <----> 商品3
|
||||
* -- key <--> 商品列表
|
||||
* ---- hKey:value <--> skuId 商品1
|
||||
* ---- hKey:value <--> 商品2
|
||||
* ---- hKey:value <--> 商品3
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class CartServiceImpl implements CartService {
|
||||
|
||||
private RedisTemplate redisTemplate;
|
||||
private SkuFeignClient skuFeignService;
|
||||
private final RedisTemplate redisTemplate;
|
||||
private final SkuFeignClient skuFeignService;
|
||||
private final CartConverter cartConverter;
|
||||
|
||||
@Override
|
||||
public List<CartItemDTO> listCartItems(Long memberId) {
|
||||
@ -65,35 +64,24 @@ public class CartServiceImpl implements CartService {
|
||||
@Override
|
||||
public boolean addCartItem(Long skuId) {
|
||||
Long memberId = SecurityUtils.getMemberId();
|
||||
if (memberId == null) {
|
||||
throw new BizException(ResultCode.INVALID_TOKEN);
|
||||
}
|
||||
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
|
||||
BoundHashOperations<String, String, CartItemDTO> cartHashOperations = getCartHashOperations(memberId);
|
||||
String hKey = String.valueOf(skuId);
|
||||
|
||||
CartItemDTO cartItem;
|
||||
// 购物车已存在该商品,更新商品数量
|
||||
if (cartHashOperations.get(hKey) != null) {
|
||||
cartItem = (CartItemDTO) cartHashOperations.get(hKey);
|
||||
CartItemDTO cartItem = cartHashOperations.get(hKey);
|
||||
|
||||
if (cartItem != null) {
|
||||
// 购物车已存在该商品,更新商品数量
|
||||
cartItem.setCount(cartItem.getCount() + 1); // 点击一次“加入购物车”,数量+1
|
||||
cartItem.setChecked(true);
|
||||
cartHashOperations.put(hKey, cartItem);
|
||||
return true;
|
||||
}
|
||||
// 购物车不存在该商品,添加商品至购物车
|
||||
cartItem = new CartItemDTO();
|
||||
CompletableFuture<Void> cartItemCompletableFuture = CompletableFuture.runAsync(() -> {
|
||||
SkuDTO skuInfo = skuFeignService.getSkuInfo(skuId).getData();
|
||||
} else {
|
||||
// 购物车中不存在该商品,新增商品到购物车
|
||||
SkuInfoDTO skuInfo = skuFeignService.getSkuInfo(skuId);
|
||||
if (skuInfo != null) {
|
||||
BeanUtil.copyProperties(skuInfo, cartItem);
|
||||
cartItem.setStock(skuInfo.getStockNum());
|
||||
cartItem = cartConverter.sku2CartItem(skuInfo);
|
||||
cartItem.setCount(1);
|
||||
cartItem.setChecked(true);
|
||||
}
|
||||
});
|
||||
CompletableFuture.allOf(cartItemCompletableFuture).join();
|
||||
|
||||
Assert.isTrue(cartItem.getSkuId() != null, "商品不存在");
|
||||
}
|
||||
cartHashOperations.put(hKey, cartItem);
|
||||
return true;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
package com.youlai.mall.oms.service.app.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.oms.mapper.OrderDeliveryMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderDelivery;
|
||||
import com.youlai.mall.oms.service.OrderDeliveryService;
|
||||
import com.youlai.mall.oms.service.app.OrderDeliveryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service("orderDeliveryService")
|
@ -1,9 +1,9 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
package com.youlai.mall.oms.service.app.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.oms.mapper.OrderItemMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.service.OrderItemService;
|
||||
import com.youlai.mall.oms.service.app.OrderItemService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
package com.youlai.mall.oms.service.app.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.security.util.SecurityUtils;
|
||||
import com.youlai.mall.oms.mapper.OrderLogMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderLog;
|
||||
import com.youlai.mall.oms.service.OrderLogService;
|
||||
import com.youlai.mall.oms.service.app.OrderLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
package com.youlai.mall.oms.service.app.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -21,31 +20,32 @@ import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.youlai.common.redis.BusinessSnGenerator;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.security.util.SecurityUtils;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.mall.oms.config.WxPayProperties;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.PayTypeEnum;
|
||||
import com.youlai.mall.oms.constant.OrderConstants;
|
||||
import com.youlai.mall.oms.converter.OrderConverter;
|
||||
import com.youlai.mall.oms.converter.OrderItemConverter;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.PaymentMethodEnum;
|
||||
import com.youlai.mall.oms.mapper.OrderMapper;
|
||||
import com.youlai.mall.oms.model.bo.OrderBO;
|
||||
import com.youlai.mall.oms.model.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.model.dto.OrderItemDTO;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.model.form.OrderPaymentForm;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OrderConfirmVO;
|
||||
import com.youlai.mall.oms.model.vo.OrderSubmitResultVO;
|
||||
import com.youlai.mall.oms.service.CartService;
|
||||
import com.youlai.mall.oms.service.OrderItemService;
|
||||
import com.youlai.mall.oms.service.OrderService;
|
||||
import com.youlai.mall.oms.model.vo.OrderPageVO;
|
||||
import com.youlai.mall.oms.service.app.CartService;
|
||||
import com.youlai.mall.oms.service.app.OrderItemService;
|
||||
import com.youlai.mall.oms.service.app.OrderService;
|
||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||
import com.youlai.mall.pms.model.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockStockDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockedSkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.ums.api.MemberFeignClient;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import io.seata.spring.annotation.GlobalTransactional;
|
||||
@ -68,13 +68,12 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.youlai.common.constant.OrderConstants.*;
|
||||
|
||||
/**
|
||||
* 订单业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/12
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ -88,10 +87,9 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final ThreadPoolExecutor threadPoolExecutor;
|
||||
private final MemberFeignClient memberFeignClient;
|
||||
private final BusinessSnGenerator businessSnGenerator;
|
||||
private final SkuFeignClient skuFeignClient;
|
||||
private final RedissonClient redissonClient;
|
||||
private final WxPayService wxPayService;
|
||||
private final RedissonClient redissonClient;
|
||||
private final OrderConverter orderConverter;
|
||||
private final OrderItemConverter orderItemConverter;
|
||||
|
||||
@ -99,11 +97,11 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
* 订单分页列表
|
||||
*/
|
||||
@Override
|
||||
public IPage<OmsOrder> listOrderPages(OrderPageQuery queryParams) {
|
||||
Page<OmsOrder> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<OmsOrder> list = this.baseMapper.listOrderPages(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
public IPage<OrderPageVO> getOrderPage(OrderPageQuery queryParams) {
|
||||
Page<OrderBO> boPage = this.baseMapper.getOrderPage(
|
||||
new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
|
||||
queryParams);
|
||||
return orderConverter.toVoPageForApp(boPage);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,140 +110,166 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
* 获取购买商品明细、用户默认收货地址、防重提交唯一token
|
||||
* 进入订单创建页面有两个入口,1:立即购买;2:购物车结算
|
||||
*
|
||||
* @param skuId 直接购买必填,购物车结算不填
|
||||
* @return
|
||||
* @param skuId 商品ID(直接购买传值)
|
||||
* @return {@link OrderConfirmVO}
|
||||
*/
|
||||
@Override
|
||||
public OrderConfirmVO confirmOrder(Long skuId) {
|
||||
OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
|
||||
// 获取原请求线程的参数
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
Long memberId = SecurityUtils.getMemberId();
|
||||
// 获取订单的商品明细信息
|
||||
CompletableFuture<Void> getOrderItemsFuture = CompletableFuture.runAsync(() -> {
|
||||
// 请求参数传递给子线程
|
||||
RequestContextHolder.setRequestAttributes(attributes);
|
||||
List<OrderItemDTO> orderItems = this.getOrderItems(skuId, memberId);
|
||||
orderConfirmVO.setOrderItems(orderItems);
|
||||
}, threadPoolExecutor);
|
||||
|
||||
// 获取会员收获地址
|
||||
CompletableFuture<Void> getMemberAddressFuture = CompletableFuture.runAsync(() -> {
|
||||
RequestContextHolder.setRequestAttributes(attributes);
|
||||
// 解决子线程无法获取HttpServletRequest请求对象中数据的问题
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
RequestContextHolder.setRequestAttributes(attributes, true);
|
||||
|
||||
// 获取订单商品
|
||||
CompletableFuture<List<OrderItemDTO>> getOrderItemsFuture = CompletableFuture.supplyAsync(
|
||||
() -> this.getOrderItems(skuId, memberId), threadPoolExecutor)
|
||||
.exceptionally(ex -> {
|
||||
log.error("Failed to get order items: {}", ex.toString());
|
||||
return null;
|
||||
});
|
||||
|
||||
// 用户收货地址
|
||||
CompletableFuture<List<MemberAddressDTO>> getMemberAddressFuture = CompletableFuture.supplyAsync(() -> {
|
||||
Result<List<MemberAddressDTO>> getMemberAddressResult = memberFeignClient.listMemberAddresses(memberId);
|
||||
List<MemberAddressDTO> memberAddresses;
|
||||
if (Result.isSuccess(getMemberAddressResult) && (memberAddresses = getMemberAddressResult.getData()) != null) {
|
||||
orderConfirmVO.setAddresses(memberAddresses);
|
||||
} else {
|
||||
orderConfirmVO.setAddresses(Collections.EMPTY_LIST);
|
||||
if (Result.isSuccess(getMemberAddressResult)) {
|
||||
return getMemberAddressResult.getData();
|
||||
}
|
||||
}, threadPoolExecutor);
|
||||
return null;
|
||||
}, threadPoolExecutor).exceptionally(ex -> {
|
||||
log.error("Failed to get addresses for memberId {} : {}", memberId, ex.toString());
|
||||
return null;
|
||||
});
|
||||
|
||||
// 进入订单确认页面生成唯一token,订单提交根据此token判断是否重复提交
|
||||
CompletableFuture<Void> getOrderTokenFuture = CompletableFuture.runAsync(() -> {
|
||||
RequestContextHolder.setRequestAttributes(attributes);
|
||||
String orderToken = businessSnGenerator.generateSerialNo("ORDER");
|
||||
orderConfirmVO.setOrderToken(orderToken);
|
||||
redisTemplate.opsForValue().set(ORDER_RESUBMIT_LOCK_PREFIX + orderToken, orderToken);
|
||||
}, threadPoolExecutor);
|
||||
// 生成唯一令牌,防止重复提交(原理:提交会消耗令牌,令牌被消耗无法再次提交)
|
||||
CompletableFuture<String> generateOrderTokenFuture = CompletableFuture.supplyAsync(() -> {
|
||||
String orderToken = this.generateTradeNo(memberId);
|
||||
redisTemplate.opsForValue().set(OrderConstants.ORDER_TOKEN_PREFIX + orderToken, orderToken);
|
||||
return orderToken;
|
||||
}, threadPoolExecutor).exceptionally(ex -> {
|
||||
log.error("Failed to generate order token .");
|
||||
return null;
|
||||
});
|
||||
|
||||
CompletableFuture.allOf(getOrderItemsFuture, getMemberAddressFuture, getOrderTokenFuture).join();
|
||||
log.info("订单确认响应:{}", orderConfirmVO);
|
||||
CompletableFuture.allOf(getOrderItemsFuture, getMemberAddressFuture, generateOrderTokenFuture).join();
|
||||
OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
|
||||
orderConfirmVO.setOrderItems(getOrderItemsFuture.join());
|
||||
orderConfirmVO.setAddresses(getMemberAddressFuture.join());
|
||||
orderConfirmVO.setOrderToken(generateOrderTokenFuture.join());
|
||||
|
||||
log.info("Order confirm response for skuId {}: {}", skuId, orderConfirmVO);
|
||||
return orderConfirmVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单提交
|
||||
*
|
||||
* @param submitForm {@link OrderSubmitForm}
|
||||
* @return 订单编号
|
||||
*/
|
||||
@Override
|
||||
@GlobalTransactional
|
||||
public OrderSubmitResultVO submitOrder(OrderSubmitForm orderSubmitForm) {
|
||||
log.info("订单提交数据:{}", JSONUtil.toJsonStr(orderSubmitForm));
|
||||
// 订单基础信息校验
|
||||
List<OrderItemDTO> orderItems = orderSubmitForm.getOrderItems();
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(orderItems), "订单没有商品");
|
||||
public String submitOrder(OrderSubmitForm submitForm) {
|
||||
log.info("订单提交参数:{}", JSONUtil.toJsonStr(submitForm));
|
||||
String orderToken = submitForm.getOrderToken();
|
||||
|
||||
// 订单重复提交校验
|
||||
String orderSn = orderSubmitForm.getOrderToken();
|
||||
Long releaseLockResult = this.redisTemplate.execute(
|
||||
new DefaultRedisScript<>(
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class
|
||||
),
|
||||
Collections.singletonList(ORDER_RESUBMIT_LOCK_PREFIX + orderSn),
|
||||
orderSn
|
||||
); // 释放锁成功则返回1
|
||||
Assert.isTrue(releaseLockResult.equals(1l), "订单重复提交,请刷新页面后重试");
|
||||
// 1. 判断订单是否重复提交(LUA脚本保证获取和删除的原子性,成功返回1,否则返回0)
|
||||
String lockAcquireScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
||||
Long lockAcquired = this.redisTemplate.execute(
|
||||
new DefaultRedisScript<>(lockAcquireScript, Long.class),
|
||||
Collections.singletonList(OrderConstants.ORDER_TOKEN_PREFIX + orderToken),
|
||||
orderToken
|
||||
);
|
||||
Assert.isTrue(lockAcquired != null && lockAcquired.equals(1L), "订单重复提交,请刷新页面后重试");
|
||||
|
||||
// 订单验价
|
||||
List<CheckPriceDTO.OrderSku> orderSkus = orderItems.stream()
|
||||
.map(orderItem -> new CheckPriceDTO.OrderSku(orderItem.getSkuId(), orderItem.getCount())
|
||||
).collect(Collectors.toList());
|
||||
|
||||
CheckPriceDTO checkPriceDTO = new CheckPriceDTO(orderSn, orderSubmitForm.getTotalAmount(), orderSkus);
|
||||
Result<Boolean> checkPriceResult = skuFeignClient.checkPrice(checkPriceDTO);
|
||||
Assert.isTrue(Result.isSuccess(checkPriceResult) && Boolean.TRUE.equals(checkPriceResult.getData()),
|
||||
"当前页面已过期,请重新刷新页面再提交");
|
||||
|
||||
// 锁定订单商品的库存
|
||||
List<LockStockDTO.LockedSku> lockedSkus = orderItems.stream()
|
||||
.map(orderItem -> new LockStockDTO.LockedSku(orderItem.getSkuId(), orderItem.getCount()))
|
||||
// 2. 订单商品校验 (PS:校验进入订单确认页面到提交过程商品(价格、上架状态)变化)
|
||||
List<OrderSubmitForm.OrderItem> orderItems = submitForm.getOrderItems();
|
||||
List<Long> skuIds = orderItems.stream()
|
||||
.map(OrderSubmitForm.OrderItem::getSkuId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
LockStockDTO lockStockDTO = new LockStockDTO(orderSn, lockedSkus);
|
||||
|
||||
Result lockStockResult = skuFeignClient.lockStock(lockStockDTO);
|
||||
Assert.isTrue(Result.isSuccess(lockStockResult), "订单提交失败:锁定商品库存失败!");
|
||||
|
||||
// 创建订单
|
||||
OmsOrder orderEntity = orderConverter.submitForm2Entity(orderSubmitForm);
|
||||
orderEntity.setStatus(OrderStatusEnum.UNPAID.getValue());
|
||||
orderEntity.setMemberId(SecurityUtils.getMemberId());
|
||||
boolean result = this.save(orderEntity);
|
||||
// 添加订单明细
|
||||
Long orderId = orderEntity.getId();
|
||||
if (result) {
|
||||
List<OmsOrderItem> itemEntities = orderItemConverter.dto2Entity(orderId, orderItems);
|
||||
result = orderItemService.saveBatch(itemEntities);
|
||||
if (result) {
|
||||
// 订单超时未支付关单
|
||||
rabbitTemplate.convertAndSend("order.exchange", "order.close.delay.routing.key", orderSn);
|
||||
}
|
||||
List<SkuInfoDTO> skuList = skuFeignClient.getSkuInfoList(skuIds);
|
||||
for (OrderSubmitForm.OrderItem item : orderItems) {
|
||||
SkuInfoDTO skuInfo = skuList.stream().filter(sku -> sku.getId().equals(item.getSkuId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
Assert.isTrue(skuInfo != null, "商品({})已下架或删除");
|
||||
Assert.isTrue(item.getPrice().compareTo(skuInfo.getPrice()) == 0, "商品({})价格发生变动,请刷新页面", item.getSkuName());
|
||||
}
|
||||
Assert.isTrue(result, "订单提交失败");
|
||||
|
||||
// 成功响应返回值构建
|
||||
OrderSubmitResultVO submitVO = new OrderSubmitResultVO(orderId, orderEntity.getOrderSn());
|
||||
return submitVO;
|
||||
// 3. 校验库存并锁定库存
|
||||
List<LockedSkuDTO> lockedSkuList = orderItems.stream()
|
||||
.map(item -> new LockedSkuDTO(item.getSkuId(), item.getQuantity(), item.getSkuSn()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
boolean lockStockResult = skuFeignClient.lockStock(orderToken, lockedSkuList);
|
||||
Assert.isTrue(lockStockResult, "订单提交失败:锁定商品库存失败!");
|
||||
|
||||
// 4. 生成订单
|
||||
boolean result = this.saveOrder(submitForm);
|
||||
log.info("order ({}) create result:{}", orderToken, result);
|
||||
return orderToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*
|
||||
* @param submitForm 订单提交表单对象
|
||||
* @return
|
||||
*/
|
||||
private boolean saveOrder(OrderSubmitForm submitForm) {
|
||||
OmsOrder order = orderConverter.form2Entity(submitForm);
|
||||
order.setStatus(OrderStatusEnum.UNPAID.getValue());
|
||||
order.setMemberId(SecurityUtils.getMemberId());
|
||||
order.setSource(submitForm.getOrderSource().getValue());
|
||||
boolean result = this.save(order);
|
||||
|
||||
Long orderId = order.getId();
|
||||
if (result) {
|
||||
|
||||
// 保存订单明细
|
||||
List<OmsOrderItem> orderItemEntities = orderItemConverter.item2Entity(submitForm.getOrderItems());
|
||||
orderItemEntities.forEach(item -> item.setOrderId(orderId));
|
||||
|
||||
orderItemService.saveBatch(orderItemEntities);
|
||||
|
||||
// 订单超时未支付取消
|
||||
rabbitTemplate.convertAndSend("order.exchange", "order.close.delay", submitForm.getOrderToken());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
* <p>
|
||||
* 余额支付:库存、余额、订单处理
|
||||
* 微信支付:生成微信支付调起参数,订单、库存、余额处理在支付回调
|
||||
*/
|
||||
@Override
|
||||
@GlobalTransactional
|
||||
public boolean payOrder(Long orderId) {
|
||||
|
||||
OmsOrder order = this.getById(orderId);
|
||||
public <T> T payOrder(OrderPaymentForm paymentForm) {
|
||||
String orderSn = paymentForm.getOrderSn();
|
||||
OmsOrder order = this.getOne(new LambdaQueryWrapper<OmsOrder>().eq(OmsOrder::getOrderSn, orderSn));
|
||||
Assert.isTrue(order != null, "订单不存在");
|
||||
|
||||
Assert.isTrue(OrderStatusEnum.UNPAID.getValue().equals(order.getStatus()), "订单不可支付,请检查订单状态");
|
||||
|
||||
RLock lock = redissonClient.getLock(ORDER_LOCK_PREFIX + order.getOrderSn());
|
||||
RLock lock = redissonClient.getLock(OrderConstants.ORDER_LOCK_PREFIX + order.getOrderSn());
|
||||
try {
|
||||
|
||||
lock.lock();
|
||||
// 扣减余额
|
||||
memberFeignClient.deductBalance(SecurityUtils.getMemberId(), order.getPayAmount());
|
||||
// 扣减库存
|
||||
skuFeignClient.deductStock(order.getOrderSn());
|
||||
// 修改订单状态 → 【已支付】
|
||||
order.setStatus(OrderStatusEnum.PAID.getValue());
|
||||
order.setPayType(PayTypeEnum.BALANCE.getValue());
|
||||
order.setPayTime(new Date());
|
||||
this.updateById(order);
|
||||
// 支付成功删除购物车已勾选的商品
|
||||
cartService.removeCheckedItem();
|
||||
return true;
|
||||
T result;
|
||||
switch (paymentForm.getPaymentMethod()) {
|
||||
case WX_JSAPI:
|
||||
result = (T) wxJsapiPay(paymentForm.getAppId(), order.getOrderSn(), order.getPaymentAmount());
|
||||
break;
|
||||
default:
|
||||
result = (T) balancePay(order);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
//释放锁
|
||||
if (lock.isLocked()) {
|
||||
@ -254,40 +278,75 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 微信支付
|
||||
* 余额支付
|
||||
*
|
||||
* @param appId
|
||||
* @param order
|
||||
* @return
|
||||
*/
|
||||
private WxPayUnifiedOrderV3Result.JsapiResult wxJsapiPay(String appId, OmsOrder order) {
|
||||
private Boolean balancePay(OmsOrder order) {
|
||||
// 扣减余额
|
||||
Long memberId = order.getMemberId();
|
||||
Long payAmount = order.getPaymentAmount();
|
||||
Result<?> deductBalanceResult = memberFeignClient.deductBalance(memberId, payAmount);
|
||||
Assert.isTrue(Result.isSuccess(deductBalanceResult), "扣减账户余额失败");
|
||||
|
||||
// 扣减库存
|
||||
skuFeignClient.deductStock(order.getOrderSn());
|
||||
|
||||
// 更新订单状态
|
||||
order.setStatus(OrderStatusEnum.PAID.getValue());
|
||||
order.setPaymentMethod(PaymentMethodEnum.BALANCE.getValue());
|
||||
order.setPaymentTime(new Date());
|
||||
this.updateById(order);
|
||||
// 支付成功删除购物车已勾选的商品
|
||||
cartService.removeCheckedItem();
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 微信支付调起
|
||||
*
|
||||
* @param appId 微信小程序ID
|
||||
* @param orderSn 订单编号
|
||||
* @param paymentAmount 支付金额
|
||||
* @return 微信支付调起参数
|
||||
*/
|
||||
private WxPayUnifiedOrderV3Result.JsapiResult wxJsapiPay(String appId, String orderSn, Long paymentAmount) {
|
||||
Long memberId = SecurityUtils.getMemberId();
|
||||
Long payAmount = order.getPayAmount();
|
||||
// 如果已经有outTradeNo了就先进行关单
|
||||
if (PayTypeEnum.WX_JSAPI.getValue().equals(order.getPayType()) && StrUtil.isNotBlank(order.getOutTradeNo())) {
|
||||
if (StrUtil.isNotBlank(orderSn)) {
|
||||
try {
|
||||
wxPayService.closeOrderV3(order.getOutTradeNo());
|
||||
wxPayService.closeOrderV3(orderSn);
|
||||
} catch (WxPayException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new BizException("微信关单异常");
|
||||
}
|
||||
}
|
||||
// 用户id前补零保证五位,对超出五位的保留后五位
|
||||
String userIdFilledZero = String.format("%05d", memberId);
|
||||
String fiveDigitsUserId = userIdFilledZero.substring(userIdFilledZero.length() - 5);
|
||||
// 在前面加上wxo(wx order)等前缀是为了人工可以快速分辨订单号是下单还是退款、来自哪家支付机构等
|
||||
// 将时间戳+3位随机数+五位id组成商户订单号,规则参考自<a href="https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html">大众点评</a>
|
||||
String outTradeNo = "wxo_" + System.currentTimeMillis() + RandomUtil.randomNumbers(3) + fiveDigitsUserId;
|
||||
log.info("商户订单号拼接完成:{}", outTradeNo);
|
||||
|
||||
// 更新订单状态
|
||||
order.setPayType(PayTypeEnum.WX_JSAPI.getValue());
|
||||
order.setOutTradeNo(outTradeNo);
|
||||
this.updateById(order);
|
||||
boolean result = this.update(new LambdaUpdateWrapper<OmsOrder>()
|
||||
.set(OmsOrder::getPaymentMethod, PaymentMethodEnum.WX_JSAPI.getValue())
|
||||
.eq(OmsOrder::getOrderSn, orderSn)
|
||||
);
|
||||
|
||||
String memberOpenId = memberFeignClient.getMemberOpenId(memberId).getData();
|
||||
|
||||
WxPayUnifiedOrderV3Request wxRequest = new WxPayUnifiedOrderV3Request().setOutTradeNo(outTradeNo).setAppid(appId).setNotifyUrl(wxPayProperties.getPayNotifyUrl()).setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(Math.toIntExact(payAmount))).setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(memberOpenId)).setDescription("赅买-订单编号" + order.getOrderSn());
|
||||
WxPayUnifiedOrderV3Request wxRequest = new WxPayUnifiedOrderV3Request()
|
||||
.setAppid(appId)
|
||||
.setOutTradeNo(orderSn)
|
||||
.setAmount(new WxPayUnifiedOrderV3Request
|
||||
.Amount()
|
||||
.setTotal(Math.toIntExact(paymentAmount))
|
||||
)
|
||||
.setPayer(
|
||||
new WxPayUnifiedOrderV3Request.Payer()
|
||||
.setOpenid(memberOpenId)
|
||||
)
|
||||
.setDescription("赅买-订单编号:" + orderSn)
|
||||
.setNotifyUrl(wxPayProperties.getPayNotifyUrl());
|
||||
WxPayUnifiedOrderV3Result.JsapiResult jsapiResult;
|
||||
try {
|
||||
jsapiResult = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, wxRequest);
|
||||
@ -299,29 +358,19 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭订单(超时未支付)
|
||||
* 关闭订单
|
||||
*
|
||||
* @param orderSn 订单编号
|
||||
* @return
|
||||
* @return 是否成功 true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean closeOrder(String orderSn) {
|
||||
OmsOrder order = this.getOne(new LambdaQueryWrapper<OmsOrder>()
|
||||
|
||||
return this.update(new LambdaUpdateWrapper<OmsOrder>()
|
||||
.eq(OmsOrder::getOrderSn, orderSn)
|
||||
.select(OmsOrder::getId, OmsOrder::getStatus)
|
||||
.eq(OmsOrder::getStatus, OrderStatusEnum.UNPAID.getValue())
|
||||
.set(OmsOrder::getStatus, OrderStatusEnum.CANCELED.getValue())
|
||||
);
|
||||
Assert.isTrue(order != null, "订单不存在");
|
||||
boolean result;
|
||||
if (OrderStatusEnum.UNPAID.getValue().equals(order.getStatus())) {
|
||||
result = this.update(new LambdaUpdateWrapper<OmsOrder>()
|
||||
.eq(OmsOrder::getId, order.getId())
|
||||
.set(OmsOrder::getStatus, OrderStatusEnum.CANCELED.getValue()));
|
||||
// 关单成功释放锁定的商品库存
|
||||
rabbitTemplate.convertAndSend("stock.exchange", "stock.release.routing.key", orderSn);
|
||||
} else { // 订单非【待付款】状态无需关闭
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,7 +408,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(result.getTradeState())) {
|
||||
orderDO.setStatus(OrderStatusEnum.PAID.getValue());
|
||||
orderDO.setTransactionId(result.getTransactionId());
|
||||
orderDO.setPayTime(new Date());
|
||||
orderDO.setPaymentTime(new Date());
|
||||
this.updateById(orderDO);
|
||||
}
|
||||
log.info("账单更新成功");
|
||||
@ -399,21 +448,38 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
List<OrderItemDTO> orderItems;
|
||||
if (skuId != null) { // 直接购买
|
||||
orderItems = new ArrayList<>();
|
||||
SkuDTO skuDTO = skuFeignClient.getSkuInfo(skuId).getData();
|
||||
SkuInfoDTO skuInfoDTO = skuFeignClient.getSkuInfo(skuId);
|
||||
OrderItemDTO orderItemDTO = new OrderItemDTO();
|
||||
BeanUtil.copyProperties(skuDTO, orderItemDTO);
|
||||
|
||||
orderItemDTO.setCount(1); // 直接购买商品的数量为1
|
||||
BeanUtil.copyProperties(skuInfoDTO, orderItemDTO);
|
||||
orderItemDTO.setSkuId(skuInfoDTO.getId());
|
||||
orderItemDTO.setQuantity(1); // 直接购买商品的数量为1
|
||||
orderItems.add(orderItemDTO);
|
||||
} else { // 购物车结算
|
||||
List<CartItemDTO> cartItems = cartService.listCartItems(memberId);
|
||||
orderItems = cartItems.stream().filter(CartItemDTO::getChecked).map(cartItem -> {
|
||||
OrderItemDTO orderItemDTO = new OrderItemDTO();
|
||||
BeanUtil.copyProperties(cartItem, orderItemDTO);
|
||||
return orderItemDTO;
|
||||
}).collect(Collectors.toList());
|
||||
orderItems = cartItems.stream()
|
||||
.filter(CartItemDTO::getChecked)
|
||||
.map(cartItem -> {
|
||||
OrderItemDTO orderItemDTO = new OrderItemDTO();
|
||||
BeanUtil.copyProperties(cartItem, orderItemDTO);
|
||||
return orderItemDTO;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
return orderItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成商户订单号
|
||||
*
|
||||
* @param memberId 会员ID
|
||||
* @return
|
||||
*/
|
||||
private String generateTradeNo(Long memberId) {
|
||||
// 用户id前补零保证五位,对超出五位的保留后五位
|
||||
String userIdFilledZero = String.format("%05d", memberId);
|
||||
String fiveDigitsUserId = userIdFilledZero.substring(userIdFilledZero.length() - 5);
|
||||
// 在前面加上wxo(wx order)等前缀是为了人工可以快速分辨订单号是下单还是退款、来自哪家支付机构等
|
||||
// 将时间戳+3位随机数+五位id组成商户订单号,规则参考自<a href="https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html">大众点评</a>
|
||||
return System.currentTimeMillis() + RandomUtil.randomNumbers(3) + fiveDigitsUserId;
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
package com.youlai.mall.oms.service.app.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.oms.mapper.OrderSettingMapper;
|
||||
import com.youlai.mall.oms.model.entity.OmsOrderSetting;
|
||||
import com.youlai.mall.oms.service.OrderSettingService;
|
||||
import com.youlai.mall.oms.service.app.OrderSettingService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -11,16 +11,11 @@ spring:
|
||||
nacos:
|
||||
# 注册中心
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
server-addr: http://192.168.179.21:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: http://localhost:8848
|
||||
server-addr: http://192.168.179.21:8848
|
||||
file-extension: yaml
|
||||
# 公共配置
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
||||
username: nacos
|
||||
password: nacos
|
||||
refresh: true
|
@ -4,16 +4,17 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.youlai.mall.oms.mapper.OrderItemMapper">
|
||||
|
||||
<!-- 根据订单ID获取订单明细 -->
|
||||
<select id="listOrderItemsByOrderId" resultType="com.youlai.mall.oms.model.vo.OrderPageVO$OrderItem">
|
||||
<!-- 根据订单ID获取订单商品列表 -->
|
||||
<select id="getOrderItemsByOrderId" resultType="com.youlai.mall.oms.model.bo.OrderBO$OrderItem">
|
||||
SELECT
|
||||
id,
|
||||
order_id,
|
||||
sku_id,
|
||||
sku_sn,
|
||||
sku_name,
|
||||
pic_url,
|
||||
price,
|
||||
count,
|
||||
quantity,
|
||||
total_amount
|
||||
FROM
|
||||
oms_order_item
|
||||
|
@ -4,68 +4,50 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.youlai.mall.oms.mapper.OrderMapper">
|
||||
|
||||
|
||||
<resultMap id="OrderPageMap" type="com.youlai.mall.oms.model.vo.OrderPageVO">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="orderSn" column="order_sn" jdbcType="VARCHAR"/>
|
||||
<result property="totalAmount" column="total_amount" jdbcType="BIGINT"/>
|
||||
<result property="payAmount" column="pay_amount" jdbcType="BIGINT"/>
|
||||
<result property="payType" column="pay_type" jdbcType="TINYINT"/>
|
||||
<result property="status" column="status" jdbcType="TINYINT"/>
|
||||
<result property="totalQuantity" column="total_quantity" jdbcType="TINYINT"/>
|
||||
<result property="createTime" column="create_time" jdbcType="VARCHAR"/>
|
||||
<result property="memberId" column="member_id" jdbcType="BIGINT"/>
|
||||
<result property="sourceType" column="source_type" jdbcType="TINYINT"/>
|
||||
<resultMap id="OrderMap" type="com.youlai.mall.oms.model.bo.OrderBO">
|
||||
<collection property="orderItems"
|
||||
column="id" select="com.youlai.mall.oms.mapper.OrderItemMapper.listOrderItemsByOrderId">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="orderId" column="order_id" jdbcType="BIGINT"/>
|
||||
<result property="skuId" column="sku_id" jdbcType="BIGINT"/>
|
||||
<result property="skuName" column="sku_name" jdbcType="VARCHAR"/>
|
||||
<result property="picUrl" column="sku_pic" jdbcType="VARCHAR"/>
|
||||
<result property="price" column="sku_price" jdbcType="BIGINT"/>
|
||||
<result property="count" column="sku_quantity" jdbcType="INTEGER"/>
|
||||
<result property="totalAmount" column="sku_total_price" jdbcType="BIGINT"/>
|
||||
<result property="spuName" column="spu_name" jdbcType="VARCHAR"/>
|
||||
column="{orderId=id}"
|
||||
select="com.youlai.mall.oms.mapper.OrderItemMapper.getOrderItemsByOrderId">
|
||||
</collection>
|
||||
</resultMap>
|
||||
|
||||
|
||||
<!-- 订单分页列表 -->
|
||||
<select id="listOrderPages" resultMap="OrderPageMap">
|
||||
<select id="getOrderPage" resultMap="OrderMap">
|
||||
|
||||
SELECT
|
||||
id,
|
||||
order_sn,
|
||||
total_amount,
|
||||
pay_amount,
|
||||
pay_type,
|
||||
status,
|
||||
total_amount,
|
||||
total_quantity,
|
||||
create_time,
|
||||
member_id,
|
||||
source_type
|
||||
FROM oms_order
|
||||
t1.id,
|
||||
t1.order_sn,
|
||||
t1.total_amount,
|
||||
t1.payment_amount,
|
||||
t1.payment_method,
|
||||
t1.status,
|
||||
t1.total_amount,
|
||||
t1.total_quantity,
|
||||
t1.create_time,
|
||||
t1.member_id,
|
||||
t1.source,
|
||||
t1.remark
|
||||
FROM
|
||||
oms_order t1
|
||||
<where>
|
||||
<if test ='queryParams.keywords !=null and queryParams.keywords.trim() neq ""' >
|
||||
AND
|
||||
(
|
||||
t1.order_sn like concat('%',#{queryParams.keywords},'%')
|
||||
)
|
||||
</if>
|
||||
<if test ='queryParams.status !=null ' >
|
||||
AND status= #{queryParams.status}
|
||||
AND t1.status= #{queryParams.status}
|
||||
</if>
|
||||
<if test ='queryParams.memberId !=null ' >
|
||||
AND member_id= #{queryParams.memberId}
|
||||
</if>
|
||||
<if test ='queryParams.orderSn !=null and queryParams.orderSn.trim() neq ""' >
|
||||
AND order_sn like concat('%',#{queryParams.orderSn},'%')
|
||||
<if test ='queryParams.beginDate !=null' >
|
||||
AND t1.create_time >= #{queryParams.beginDate}
|
||||
</if>
|
||||
|
||||
<if test ='queryParams.beginDate !=null and queryParams.beginDate.trim() neq ""' >
|
||||
AND date_format (create_time,'%Y-%m-%d') >= date_format(#{queryParams.beginDate},'%Y-%m-%d')
|
||||
</if>
|
||||
|
||||
<if test ='queryParams.endDate !=null and queryParams.endDate.trim() neq ""' >
|
||||
AND date_format (create_time,'%Y-%m-%d') <= date_format(#{queryParams.endDate},'%Y-%m-%d')
|
||||
<if test ='queryParams.endDate !=null' >
|
||||
AND t1.create_time <= #{queryParams.endDate}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
ORDER BY
|
||||
t1.create_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
|
@ -0,0 +1,242 @@
|
||||
package com.youlai.mall.oms.controller;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.youlai.mall.oms.enums.OrderSourceEnum;
|
||||
import com.youlai.mall.oms.enums.PaymentMethodEnum;
|
||||
import com.youlai.mall.oms.model.form.OrderPaymentForm;
|
||||
import com.youlai.mall.oms.model.form.OrderSubmitForm;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* 订单单元测试
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Slf4j
|
||||
public class OrderControllerTests {
|
||||
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
private final String mobile = "18866668888";// 商城会员手机号
|
||||
private final String verifyCode = "666666";// 短信验证码,666666是免校验验证码
|
||||
|
||||
private final Long skuId = 1L;// 购买商品ID
|
||||
|
||||
/**
|
||||
* 购买商品-正常流程测试
|
||||
*/
|
||||
@Test
|
||||
void testPurchaseFlow_Normal() throws Exception {
|
||||
|
||||
// 会员登录
|
||||
String accessToken = acquireTokenByLogin(mobile, verifyCode); // 获取 accessToken,填充请求头用于身份认证
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(accessToken);
|
||||
|
||||
// 添加购物车
|
||||
this.addToCard(skuId, headers);
|
||||
|
||||
// 订单确认
|
||||
String orderToken = this.confirmOrder(headers); // 返回订单提交令牌,用于订单提交
|
||||
|
||||
// 订单提交
|
||||
String orderSn = this.submitOrder(orderToken, headers); // 返回订单编号,用于订单支付
|
||||
|
||||
// 订单支付
|
||||
this.payOrder(orderSn, headers); // 支付成功,商品库存扣减,账户余额扣减,订单状态改变(待支付 → 待发货)
|
||||
}
|
||||
|
||||
/**
|
||||
* 购买商品-超时未支付流程测试
|
||||
*/
|
||||
@Test
|
||||
void testPurchaseFlow_PaymentTimeout() throws Exception {
|
||||
|
||||
// 会员登录
|
||||
String accessToken = acquireTokenByLogin(mobile, verifyCode); // 获取 accessToken,填充请求头用于身份认证
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(accessToken);
|
||||
|
||||
// 添加购物车
|
||||
this.addToCard(skuId, headers);
|
||||
|
||||
// 订单确认
|
||||
String orderToken = this.confirmOrder(headers); // 返回订单提交令牌,用于订单提交
|
||||
|
||||
// 订单提交
|
||||
String orderSn = this.submitOrder(orderToken, headers); // 返回订单编号,用于订单支付
|
||||
|
||||
// 模拟等待超过支付超时时间
|
||||
Thread.sleep(30 * 1000); // OrderRabbitConfig#orderDelayQueue#x-message-ttl 设置10s未支付取消
|
||||
|
||||
// 订单支付
|
||||
this.payOrder(orderSn, headers); // 此处支付会异常,因为超时未支付,订单已被系统自动取消,无法再进行支付
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加商品至购物车
|
||||
*/
|
||||
private void addToCard(Long skuId, HttpHeaders headers) throws Exception {
|
||||
mockMvc.perform(post("/app-api/v1/carts")
|
||||
.param("skuId", String.valueOf(skuId))
|
||||
.headers(headers))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单确认
|
||||
*/
|
||||
private String confirmOrder(HttpHeaders headers) throws Exception {
|
||||
MvcResult confirmResult = mockMvc.perform(
|
||||
post("/app-api/v1/orders/confirm")
|
||||
.headers(headers)
|
||||
).andExpect(status().isOk())
|
||||
.andReturn();
|
||||
String confirmJsonResponse = confirmResult.getResponse().getContentAsString();
|
||||
log.info("订单确认响应:{}", confirmJsonResponse);
|
||||
JsonNode confirmJsonNode = objectMapper.readTree(confirmJsonResponse);
|
||||
return confirmJsonNode.path("data").path("orderToken").asText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单提交
|
||||
*/
|
||||
private String submitOrder(String orderToken, HttpHeaders headers) throws Exception {
|
||||
// 构造请求体
|
||||
OrderSubmitForm submitForm = new OrderSubmitForm();
|
||||
|
||||
// submitForm - 商品列表
|
||||
OrderSubmitForm.OrderItem orderItem = new OrderSubmitForm.OrderItem();
|
||||
orderItem.setSkuId(skuId);
|
||||
orderItem.setQuantity(1);
|
||||
orderItem.setSkuName("REDMI K60 16G+1T");
|
||||
orderItem.setSkuSn("sn001");
|
||||
orderItem.setSpuName("REDMI K60");
|
||||
orderItem.setPrice(399900L);
|
||||
orderItem.setPicUrl("https://www.youlai.tech/files/default/c25b39470474494485633c49101a0f5d.png");
|
||||
submitForm.setOrderItems(Arrays.asList(orderItem));
|
||||
// submitForm - 收货地址
|
||||
OrderSubmitForm.ShippingAddress shippingAddress = new OrderSubmitForm.ShippingAddress();
|
||||
shippingAddress.setProvince("上海");
|
||||
shippingAddress.setCity("上海市");
|
||||
shippingAddress.setDistrict("浦东新区");
|
||||
shippingAddress.setConsigneeName("法外张三");
|
||||
shippingAddress.setConsigneeMobile("18866668888");
|
||||
shippingAddress.setDetailAddress("世纪公园");
|
||||
submitForm.setShippingAddress(shippingAddress);
|
||||
|
||||
// submitForm - 订单信息
|
||||
submitForm.setOrderToken(orderToken);
|
||||
submitForm.setPaymentAmount(orderItem.getPrice() * 1);
|
||||
submitForm.setOrderSource(OrderSourceEnum.APP);
|
||||
submitForm.setRemark("单元测试生成订单");
|
||||
|
||||
// 发起 POST 请求
|
||||
MockHttpServletRequestBuilder requestBuilder = post("/app-api/v1/orders/submit")
|
||||
.headers(headers)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(submitForm));
|
||||
|
||||
// 执行请求并断言结果
|
||||
MvcResult submitResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
String confirmJsonResponse = submitResult.getResponse().getContentAsString();
|
||||
JsonNode confirmJsonNode = objectMapper.readTree(confirmJsonResponse);
|
||||
|
||||
return confirmJsonNode.path("data").asText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
*/
|
||||
private void payOrder(String orderSn, HttpHeaders headers) throws Exception {
|
||||
|
||||
OrderPaymentForm paymentForm = new OrderPaymentForm();
|
||||
paymentForm.setOrderSn(orderSn);
|
||||
paymentForm.setPaymentMethod(PaymentMethodEnum.BALANCE);
|
||||
mockMvc.perform(post("/app-api/v1/orders/payment")
|
||||
.headers(headers)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(paymentForm))
|
||||
).andExpect(status().isOk())
|
||||
.andDo(MockMvcResultHandlers.print());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 登录获取访问令牌
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param verifyCode 短信验证码
|
||||
* @return
|
||||
*/
|
||||
private String acquireTokenByLogin(String mobile, String verifyCode) {
|
||||
String clientId = "mall-app";
|
||||
String clientSecret = "123456";
|
||||
String tokenUrl = "http://localhost:9000/oauth2/token";
|
||||
|
||||
// 构建请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
// 构建请求体
|
||||
MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
|
||||
requestBody.add("grant_type", "sms_code");
|
||||
requestBody.add("client_id", clientId);
|
||||
requestBody.add("client_secret", clientSecret);
|
||||
requestBody.add("mobile", mobile);
|
||||
requestBody.add("code", verifyCode);
|
||||
|
||||
// 创建 Basic Auth 头部
|
||||
String authHeader = clientId + ":" + clientSecret;
|
||||
String encodedAuthHeader = Base64.getEncoder().encodeToString(authHeader.getBytes());
|
||||
headers.set("Authorization", "Basic " + encodedAuthHeader);
|
||||
|
||||
// 创建请求实体
|
||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
// 发送请求
|
||||
String jsonStr = restTemplate.postForEntity(tokenUrl, requestEntity, String.class).getBody();
|
||||
|
||||
return JSONUtil.parseObj(jsonStr).getJSONObject("data").getStr("access_token");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.youlai.mall.oms.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
@Slf4j
|
||||
public class RabbitMQTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void payOrderTest() {
|
||||
rabbitTemplate.convertAndSend("order.exchange", "order.create.routing.key", "4acd475a-c6aa-4d9a-a3a5-40da7472cbee");
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OmsOrderPageVO;
|
||||
import com.youlai.mall.oms.service.admin.OmsOrderService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
@Slf4j
|
||||
class OmsOrderServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private OmsOrderService omsOrderService;
|
||||
|
||||
@Test
|
||||
void testGetOrderPage() {
|
||||
OrderPageQuery queryParams = new OrderPageQuery();
|
||||
queryParams.setPageNum(1);
|
||||
queryParams.setPageSize(10);
|
||||
queryParams.setBeginDate(DateUtil.parseDate("2022-01-01"));
|
||||
queryParams.setEndDate(DateUtil.parseDate("2025-01-01"));
|
||||
|
||||
IPage<OmsOrderPageVO> orderPage = omsOrderService.getOrderPage(queryParams);
|
||||
|
||||
log.info(JSONUtil.toJsonStr(orderPage));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.youlai.mall.oms.model.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.model.vo.OrderPageVO;
|
||||
import com.youlai.mall.oms.service.app.OrderService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
@Slf4j
|
||||
class OrderServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
void testGetOrderPage() {
|
||||
OrderPageQuery queryParams = new OrderPageQuery();
|
||||
queryParams.setPageNum(1);
|
||||
queryParams.setPageSize(10);
|
||||
queryParams.setBeginDate(DateUtil.parseDate("2022-01-01"));
|
||||
queryParams.setEndDate(DateUtil.parseDate("2025-01-01"));
|
||||
|
||||
IPage<OrderPageVO> orderPage = orderService.getOrderPage(queryParams);
|
||||
|
||||
log.info(JSONUtil.toJsonStr(orderPage));
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,12 @@
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
|
@ -1,48 +1,53 @@
|
||||
package com.youlai.mall.pms.api;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.model.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockStockDTO;
|
||||
import com.youlai.common.web.config.FeignDecoderConfig;
|
||||
import com.youlai.mall.pms.model.dto.LockedSkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(value = "mall-pms", contextId = "sku")
|
||||
|
||||
@FeignClient(value = "mall-pms", contextId = "sku", configuration = {FeignDecoderConfig.class})
|
||||
public interface SkuFeignClient {
|
||||
|
||||
/**
|
||||
* 获取商品库存单元信息
|
||||
* 获取商品库存信息
|
||||
*/
|
||||
@GetMapping("/app-api/v1/sku/{skuId}/info")
|
||||
Result<SkuDTO> getSkuInfo(@PathVariable Long skuId);
|
||||
@GetMapping("/app-api/v1/skus/{skuId}")
|
||||
SkuInfoDTO getSkuInfo(@PathVariable Long skuId);
|
||||
|
||||
/**
|
||||
* 获取商品库存信息列表
|
||||
*
|
||||
* @param skuIds SKU ID 列表
|
||||
* @return 商品库存信息列表
|
||||
*/
|
||||
@GetMapping("/app-api/v1/skus")
|
||||
List<SkuInfoDTO> getSkuInfoList(@RequestParam List<Long> skuIds);
|
||||
|
||||
/**
|
||||
* 锁定商品库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/sku/_lock")
|
||||
Result lockStock(@RequestBody LockStockDTO lockStockDTO);
|
||||
@PutMapping("/app-api/v1/skus/lock")
|
||||
boolean lockStock(@RequestParam String orderToken, @RequestBody List<LockedSkuDTO> lockedSkuList);
|
||||
|
||||
/**
|
||||
* 解锁商品库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/sku/_unlock")
|
||||
Result unlockStock(@RequestParam String orderSn);
|
||||
@PutMapping("/app-api/v1/skus/unlock")
|
||||
boolean unlockStock(@RequestParam String orderSn);
|
||||
|
||||
/**
|
||||
* 扣减订单商品库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/sku/_deduct")
|
||||
Result deductStock(@RequestParam String orderSn);
|
||||
|
||||
/**
|
||||
* 订单商品验价
|
||||
* <p>
|
||||
* 扣减指定订单商品的库存数量。
|
||||
*
|
||||
* @param checkPriceDTO
|
||||
* @param orderSn 订单编号
|
||||
* @return 扣减库存结果
|
||||
*/
|
||||
@PostMapping("/app-api/v1/sku/price/_check")
|
||||
Result<Boolean> checkPrice(@RequestBody CheckPriceDTO checkPriceDTO);
|
||||
|
||||
|
||||
@PutMapping("/app-api/v1/skus/deduct")
|
||||
boolean deductStock(@RequestParam String orderSn);
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import java.util.List;
|
||||
* 订单商品验价传输对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/7
|
||||
* @date 2022/2/7
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@ -19,20 +19,15 @@ import java.util.List;
|
||||
@ToString
|
||||
public class CheckPriceDTO {
|
||||
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
private String orderSn;
|
||||
|
||||
/**
|
||||
* 订单总金额
|
||||
*/
|
||||
private Long orderTotalAmount;
|
||||
private Long totalAmount;
|
||||
|
||||
/**
|
||||
* 订单商品明细
|
||||
*/
|
||||
private List<OrderSku> orderSkus;
|
||||
private List<OrderSku> skus;
|
||||
|
||||
/**
|
||||
* 订单商品对象
|
||||
|
@ -1,49 +0,0 @@
|
||||
package com.youlai.mall.pms.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 锁定库存传输对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/20
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LockStockDTO {
|
||||
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
private String orderSn;
|
||||
|
||||
/**
|
||||
* 锁定商品集合
|
||||
*/
|
||||
private List<LockedSku> lockedSkus;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class LockedSku {
|
||||
|
||||
/**
|
||||
* 锁定商品ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.youlai.mall.pms.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* 锁定库存传输对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LockedSkuDTO {
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
|
||||
/**
|
||||
* 商品编码
|
||||
*/
|
||||
private String skuSn;
|
||||
|
||||
|
||||
|
||||
}
|
@ -3,18 +3,20 @@ package com.youlai.mall.pms.model.dto;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* SKU信息传输对象
|
||||
* 商品库存信息DTO
|
||||
* <p>
|
||||
* 用于表示商品的库存信息。
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/5 23:09
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class SkuDTO {
|
||||
public class SkuInfoDTO {
|
||||
/**
|
||||
* skuId
|
||||
* SKU的唯一标识符
|
||||
*/
|
||||
private Long skuId;
|
||||
private Long id;
|
||||
/**
|
||||
* SKU 编号
|
||||
*/
|
||||
@ -34,9 +36,9 @@ public class SkuDTO {
|
||||
/**
|
||||
* SKU 库存数量
|
||||
*/
|
||||
private Integer stockNum;
|
||||
private Integer stock;
|
||||
/**
|
||||
* SPU 名称
|
||||
* 所属SPU的名称
|
||||
*/
|
||||
private String spuName;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.youlai.mall.pms.common.constant;
|
||||
|
||||
/**
|
||||
* 商品常量
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface ProductConstants {
|
||||
|
||||
/**
|
||||
* 锁定的商品列表缓存键前缀
|
||||
*/
|
||||
String LOCKED_SKUS_PREFIX = "product:locked_skus:";
|
||||
|
||||
/**
|
||||
* 商品分布式锁缓存键前缀
|
||||
*/
|
||||
String SKU_LOCK_PREFIX = "product:sku_lock:";
|
||||
|
||||
/**
|
||||
* 临时规格ID前缀
|
||||
*/
|
||||
String SPEC_TEMP_ID_PREFIX = "tid_";
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.mall.pms.enums;
|
||||
package com.youlai.mall.pms.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@ -6,7 +6,7 @@ import lombok.Getter;
|
||||
* 商品属性类型枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/20
|
||||
* @date 2022/12/20
|
||||
*/
|
||||
public enum AttributeTypeEnum {
|
||||
|
@ -0,0 +1,69 @@
|
||||
package com.youlai.mall.pms.common.util;
|
||||
|
||||
import com.google.common.hash.Funnel;
|
||||
import com.google.common.hash.Hashing;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* 布隆过滤器,摘录自Google-guava包
|
||||
*
|
||||
* @author DaniR
|
||||
* @date 2021/6/23 20:30
|
||||
*/
|
||||
public class BloomFilterUtils<T> {
|
||||
private final int numHashFunctions;
|
||||
|
||||
private final int bitSize;
|
||||
|
||||
private final Funnel<T> funnel;
|
||||
|
||||
|
||||
public BloomFilterUtils(Funnel<T> funnel, int expectedInsertions, double fpp) {
|
||||
checkArgument(funnel != null, "funnel不能为空");
|
||||
checkArgument(
|
||||
expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions);
|
||||
checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
|
||||
checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
|
||||
this.funnel = funnel;
|
||||
// 计算bit数组长度
|
||||
bitSize = optimalNumOfBits(expectedInsertions, fpp);
|
||||
// 计算hash方法执行次数
|
||||
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
|
||||
}
|
||||
|
||||
public int[] murmurHash(T value) {
|
||||
int[] offset = new int[numHashFunctions];
|
||||
|
||||
long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
|
||||
int hash1 = (int) hash64;
|
||||
int hash2 = (int) (hash64 >>> 32);
|
||||
for (int i = 1; i <= numHashFunctions; i++) {
|
||||
int combinedHash = hash1 + i * hash2;
|
||||
if (combinedHash < 0) {
|
||||
combinedHash = ~combinedHash;
|
||||
}
|
||||
offset[i - 1] = combinedHash % bitSize;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算bit数组长度
|
||||
*/
|
||||
private int optimalNumOfBits(long n, double p) {
|
||||
if (p == 0) {
|
||||
// 设定最小期望长度
|
||||
p = Double.MIN_VALUE;
|
||||
}
|
||||
return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算hash方法执行次数
|
||||
*/
|
||||
private int optimalNumOfHashFunctions(long n, long m) {
|
||||
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 「管理端」商品控制层
|
||||
* ADMIN-商品控制层
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2021/1/4
|
||||
@ -28,38 +28,38 @@ public class PmsSpuController {
|
||||
|
||||
private SpuService spuServiced;
|
||||
|
||||
@Operation(summary= "商品分页列表")
|
||||
@Operation(summary = "商品分页列表")
|
||||
@GetMapping("/pages")
|
||||
public PageResult listPmsSpuPages(SpuPageQuery queryParams) {
|
||||
IPage<PmsSpuPageVO> result = spuServiced.listPmsSpuPages(queryParams);
|
||||
IPage<PmsSpuPageVO> result = spuServiced.getSpuPage(queryParams);
|
||||
return PageResult.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "商品详情")
|
||||
@Operation(summary = "商品详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result detail( @Parameter(name = "商品ID") @PathVariable Long id) {
|
||||
public Result detail(@Parameter(name = "商品ID") @PathVariable Long id) {
|
||||
PmsSpuDetailVO pmsSpuDetailVO = spuServiced.getPmsSpuDetail(id);
|
||||
return Result.success(pmsSpuDetailVO);
|
||||
}
|
||||
|
||||
@Operation(summary= "新增商品")
|
||||
@Operation(summary = "新增商品")
|
||||
@PostMapping
|
||||
public Result addSpu(@RequestBody PmsSpuForm formData) {
|
||||
boolean result = spuServiced.addSpu(formData);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "修改商品")
|
||||
@Operation(summary = "修改商品")
|
||||
@PutMapping(value = "/{id}")
|
||||
public Result updateSpuById(
|
||||
@Parameter(name = "商品ID") @PathVariable Long id,
|
||||
@RequestBody PmsSpuForm formData
|
||||
) {
|
||||
boolean result = spuServiced.updateSpuById(id,formData);
|
||||
boolean result = spuServiced.updateSpuById(id, formData);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "删除商品")
|
||||
@Operation(summary = "删除商品")
|
||||
@DeleteMapping("/{ids}")
|
||||
public Result delete(@Parameter(name = "商品ID,多个以英文逗号(,)分隔") @PathVariable String ids) {
|
||||
boolean result = spuServiced.removeBySpuIds(ids);
|
||||
|
@ -3,9 +3,9 @@ package com.youlai.mall.pms.controller.app;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.model.vo.CategoryVO;
|
||||
import com.youlai.mall.pms.service.CategoryService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -27,10 +27,9 @@ public class CategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
@Operation(summary= "分类列表")
|
||||
@Operation(summary = "分类列表")
|
||||
@GetMapping
|
||||
public Result list(
|
||||
@Parameter(name = "上级分类ID") Long parentId) {
|
||||
public Result list(@Parameter(name = "上级分类ID") Long parentId) {
|
||||
List<CategoryVO> list = categoryService.listCategory(parentId);
|
||||
return Result.success(list);
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
package com.youlai.mall.pms.controller.app;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.model.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockStockDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockedSkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.service.SkuService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -11,63 +10,61 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SKU控制层
|
||||
* 商品库存
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/21
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Tag(name = "「移动端」SKU接口")
|
||||
@Tag(name = "「移动端」商品库存接口")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/sku")
|
||||
@RequestMapping("/app-api/v1/skus")
|
||||
@RequiredArgsConstructor
|
||||
public class SkuController {
|
||||
|
||||
private final SkuService skuService;
|
||||
|
||||
@Operation(summary = "获取商品库存信息")
|
||||
@GetMapping("/{skuId}/info")
|
||||
public Result<SkuDTO> getSkuInfo(
|
||||
@Parameter(name = "商品ID") @PathVariable Long skuId
|
||||
@GetMapping("/{skuId}")
|
||||
public Result<SkuInfoDTO> getSkuInfo(
|
||||
@Parameter(name ="商品ID") @PathVariable Long skuId
|
||||
) {
|
||||
SkuDTO skuInfo = skuService.getSkuInfo(skuId);
|
||||
SkuInfoDTO skuInfo = skuService.getSkuInfo(skuId);
|
||||
return Result.success(skuInfo);
|
||||
}
|
||||
|
||||
@Operation(summary ="获取商品库存数量")
|
||||
@GetMapping("/{skuId}/stock_num")
|
||||
public Result<Integer> getStockNum(
|
||||
@Parameter(name = "商品ID") @PathVariable Long skuId
|
||||
@Operation(summary = "获取商品库存列表")
|
||||
@GetMapping
|
||||
public Result<List<SkuInfoDTO>> getSkuInfoList(
|
||||
@Parameter(name ="SKU ID 列表") @RequestParam List<Long> skuIds
|
||||
) {
|
||||
Integer stockNum = skuService.getStockNum(skuId);
|
||||
return Result.success(stockNum);
|
||||
List<SkuInfoDTO> skuInfos = skuService.listSkuInfos(skuIds);
|
||||
return Result.success(skuInfos);
|
||||
}
|
||||
|
||||
@Operation(summary = "锁定库存")
|
||||
@PutMapping("/_lock")
|
||||
public Result lockStock(@RequestBody LockStockDTO lockStockDTO) {
|
||||
boolean lockResult = skuService.lockStock(lockStockDTO);
|
||||
return Result.success(lockResult);
|
||||
@Operation(summary = "校验并锁定库存")
|
||||
@PutMapping("/lock")
|
||||
public Result<?> lockStock(
|
||||
@RequestParam String orderToken,
|
||||
@RequestBody List<LockedSkuDTO> lockedSkuList
|
||||
) {
|
||||
boolean lockStockResult = skuService.lockStock(orderToken,lockedSkuList);
|
||||
return Result.success(lockStockResult);
|
||||
}
|
||||
|
||||
@Operation(summary = "解锁库存")
|
||||
@PutMapping("/_unlock")
|
||||
public Result<Boolean> unlockStock(String orderToken) {
|
||||
@PutMapping("/unlock")
|
||||
public Result<?> unlockStock(String orderToken) {
|
||||
boolean result = skuService.unlockStock(orderToken);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "扣减库存")
|
||||
@PutMapping("/_deduct")
|
||||
public Result<Boolean> deductStock(String orderToken) {
|
||||
boolean result = skuService.deductStock(orderToken);
|
||||
@PutMapping("/deduct")
|
||||
public Result<?> deductStock(String orderSn) {
|
||||
boolean result = skuService.deductStock(orderSn);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "商品验价")
|
||||
@PostMapping("/price/_check")
|
||||
public Result<Boolean> checkPrice(@RequestBody CheckPriceDTO checkPriceDTO) {
|
||||
boolean result = skuService.checkPrice(checkPriceDTO);
|
||||
return Result.success(result);
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import com.youlai.common.result.PageResult;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.model.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.model.vo.SeckillingSpuVO;
|
||||
import com.youlai.mall.pms.model.vo.SpuPageVO;
|
||||
import com.youlai.mall.pms.model.vo.SpuDetailVO;
|
||||
import com.youlai.mall.pms.model.vo.SpuPageVO;
|
||||
import com.youlai.mall.pms.service.SpuService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "「移动端」商品接口")
|
||||
@Tag(name = "「移动端」商品接口")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/spu")
|
||||
@RequiredArgsConstructor
|
||||
@ -27,23 +27,23 @@ public class SpuController {
|
||||
|
||||
private final SpuService spuService;
|
||||
|
||||
@Operation(summary= "商品分页列表")
|
||||
@Operation(summary = "商品分页列表")
|
||||
@GetMapping("/pages")
|
||||
public PageResult listSpuPages(SpuPageQuery queryParams) {
|
||||
IPage<SpuPageVO> result = spuService.listSpuPages(queryParams);
|
||||
public PageResult getSpuPageForApp(SpuPageQuery queryParams) {
|
||||
IPage<SpuPageVO> result = spuService.getSpuPageForApp(queryParams);
|
||||
return PageResult.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary= "获取商品详情")
|
||||
@Operation(summary = "获取商品详情")
|
||||
@GetMapping("/{spuId}")
|
||||
public Result<SpuDetailVO> getSpuDetail(
|
||||
@Parameter(name = "商品ID") @PathVariable Long spuId
|
||||
@Parameter(name ="商品ID") @PathVariable Long spuId
|
||||
) {
|
||||
SpuDetailVO spuDetailVO = spuService.getSpuDetail(spuId);
|
||||
return Result.success(spuDetailVO);
|
||||
}
|
||||
|
||||
@Operation(summary= "获取秒杀商品列表")
|
||||
@Operation(summary = "获取秒杀商品列表")
|
||||
@GetMapping("/seckilling")
|
||||
public Result<List<SeckillingSpuVO>> listSeckillingSpu() {
|
||||
List<SeckillingSpuVO> list = spuService.listSeckillingSpu();
|
||||
|
@ -0,0 +1,21 @@
|
||||
package com.youlai.mall.pms.converter;
|
||||
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.model.entity.PmsSku;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品对象转换器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/6/11
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface SkuConverter {
|
||||
|
||||
SkuInfoDTO entity2SkuInfoDto(PmsSku entity);
|
||||
|
||||
List<SkuInfoDTO> entity2SkuInfoDto(List<PmsSku> list);
|
||||
}
|
@ -10,7 +10,7 @@ import org.mapstruct.Mappings;
|
||||
* 商品属性对象转换器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/6/11
|
||||
* @date 2022/6/11
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface SpuAttributeConverter {
|
||||
|
@ -14,7 +14,7 @@ import java.util.List;
|
||||
* 商品对象转换器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/6/11
|
||||
* @date 2022/6/11
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface SpuConverter {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.youlai.mall.pms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.model.entity.PmsSku;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@ -14,5 +14,5 @@ public interface PmsSkuMapper extends BaseMapper<PmsSku> {
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
SkuDTO getSkuInfo(Long skuId);
|
||||
SkuInfoDTO getSkuInfo(Long skuId);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ public interface PmsSpuMapper extends BaseMapper<PmsSpu> {
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
List<PmsSpuPageVO> listPmsSpuPages(Page<PmsSpuPageVO> page, SpuPageQuery queryParams);
|
||||
List<PmsSpuPageVO> getSpuPage(Page<PmsSpuPageVO> page, SpuPageQuery queryParams);
|
||||
|
||||
/**
|
||||
* 「应用端」商品分页列表
|
||||
@ -29,7 +29,7 @@ public interface PmsSpuMapper extends BaseMapper<PmsSpu> {
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
List<SpuPageVO> listSpuPages(Page<SpuPageVO> page, SpuPageQuery queryParams);
|
||||
List<SpuPageVO> getSpuPageForApp(Page<SpuPageVO> page, SpuPageQuery queryParams);
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.youlai.mall.pms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.pms.model.form.PmsCategoryAttributeForm;
|
||||
import com.youlai.mall.pms.model.entity.PmsCategoryAttribute;
|
||||
import com.youlai.mall.pms.model.form.PmsCategoryAttributeForm;
|
||||
|
||||
public interface AttributeService extends IService<PmsCategoryAttribute> {
|
||||
|
||||
|
@ -1,41 +1,45 @@
|
||||
package com.youlai.mall.pms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.pms.model.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockedSkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.model.entity.PmsSku;
|
||||
import com.youlai.mall.pms.model.dto.LockStockDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 商品库存单元接口
|
||||
* 商品库存接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/5 17:11
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface SkuService extends IService<PmsSku> {
|
||||
|
||||
/**
|
||||
* 获取商品的库存数量
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
Integer getStockNum(Long skuId);
|
||||
|
||||
/**
|
||||
* 获取商品库存信息
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
* @param skuId SKU ID
|
||||
* @return 商品库存信息
|
||||
*/
|
||||
SkuDTO getSkuInfo(Long skuId);
|
||||
|
||||
SkuInfoDTO getSkuInfo(Long skuId);
|
||||
|
||||
/**
|
||||
* 锁定库存
|
||||
* 获取商品库存信息列表
|
||||
*
|
||||
* @param skuIds SKU ID 列表
|
||||
* @return 商品库存信息列表
|
||||
*/
|
||||
boolean lockStock(LockStockDTO lockStockDTO);
|
||||
List<SkuInfoDTO> listSkuInfos(List<Long> skuIds);
|
||||
|
||||
/**
|
||||
* 校验并锁定库存
|
||||
*
|
||||
* @param orderToken 订单临时编号 (此时订单未创建)
|
||||
* @param lockedSkuList 锁定商品库存信息列表
|
||||
* @return true/false
|
||||
*/
|
||||
boolean lockStock(String orderToken,List<LockedSkuDTO> lockedSkuList);
|
||||
|
||||
/**
|
||||
* 解锁库存
|
||||
@ -48,12 +52,4 @@ public interface SkuService extends IService<PmsSku> {
|
||||
boolean deductStock(String orderSn);
|
||||
|
||||
|
||||
/**
|
||||
* 商品验价
|
||||
*
|
||||
* @param checkPriceDTO
|
||||
* @return
|
||||
*/
|
||||
boolean checkPrice(CheckPriceDTO checkPriceDTO);
|
||||
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package com.youlai.mall.pms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.pms.model.form.PmsSpuForm;
|
||||
import com.youlai.mall.pms.model.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.model.form.PmsSpuForm;
|
||||
import com.youlai.mall.pms.model.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.model.vo.*;
|
||||
|
||||
@ -13,7 +13,7 @@ import java.util.List;
|
||||
* 商品业务接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/2/5
|
||||
* @date 2022/2/5
|
||||
*/
|
||||
public interface SpuService extends IService<PmsSpu> {
|
||||
|
||||
@ -24,7 +24,7 @@ public interface SpuService extends IService<PmsSpu> {
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
IPage<PmsSpuPageVO> listPmsSpuPages(SpuPageQuery queryParams);
|
||||
IPage<PmsSpuPageVO> getSpuPage(SpuPageQuery queryParams);
|
||||
|
||||
/**
|
||||
* 「应用端」商品分页列表
|
||||
@ -32,7 +32,7 @@ public interface SpuService extends IService<PmsSpu> {
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
IPage<SpuPageVO> listSpuPages(SpuPageQuery queryParams);
|
||||
IPage<SpuPageVO> getSpuPageForApp(SpuPageQuery queryParams);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -4,8 +4,8 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.pms.mapper.PmsCategoryAttributeMapper;
|
||||
import com.youlai.mall.pms.model.form.PmsCategoryAttributeForm;
|
||||
import com.youlai.mall.pms.model.entity.PmsCategoryAttribute;
|
||||
import com.youlai.mall.pms.model.form.PmsCategoryAttributeForm;
|
||||
import com.youlai.mall.pms.service.AttributeService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -17,7 +17,7 @@ import java.util.stream.Collectors;
|
||||
* 商品属性业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/7/2
|
||||
* @date 2022/7/2
|
||||
*/
|
||||
@Service
|
||||
public class AttributeServiceImpl extends ServiceImpl<PmsCategoryAttributeMapper, PmsCategoryAttribute> implements AttributeService {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.youlai.mall.pms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.pms.model.entity.PmsBrand;
|
||||
import com.youlai.mall.pms.mapper.PmsBrandMapper;
|
||||
import com.youlai.mall.pms.model.entity.PmsBrand;
|
||||
import com.youlai.mall.pms.service.BrandService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -5,10 +5,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.common.web.model.Option;
|
||||
import com.youlai.mall.pms.model.entity.PmsCategory;
|
||||
import com.youlai.mall.pms.mapper.PmsCategoryMapper;
|
||||
import com.youlai.mall.pms.service.CategoryService;
|
||||
import com.youlai.mall.pms.model.entity.PmsCategory;
|
||||
import com.youlai.mall.pms.model.vo.CategoryVO;
|
||||
import com.youlai.mall.pms.service.CategoryService;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -2,30 +2,29 @@ package com.youlai.mall.pms.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.constant.ProductConstants;
|
||||
import com.youlai.mall.pms.common.constant.ProductConstants;
|
||||
import com.youlai.mall.pms.converter.SkuConverter;
|
||||
import com.youlai.mall.pms.mapper.PmsSkuMapper;
|
||||
import com.youlai.mall.pms.model.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockStockDTO;
|
||||
import com.youlai.mall.pms.model.dto.LockedSkuDTO;
|
||||
import com.youlai.mall.pms.model.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.model.entity.PmsSku;
|
||||
import com.youlai.mall.pms.service.SkuService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 商品SKU业务实现类
|
||||
* 商品库存业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/12/21
|
||||
@ -37,68 +36,68 @@ public class SkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> implements
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
private final RedissonClient redissonClient;
|
||||
private final SkuConverter skuConverter;
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品库存数量
|
||||
* 获取商品库存信息
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
* @param skuId SKU ID
|
||||
* @return 商品库存信息
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(cacheNames = "pms", key = "'stock_num:'+#skuId")
|
||||
public Integer getStockNum(Long skuId) {
|
||||
|
||||
PmsSku pmsSku = this.getOne(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getId, skuId)
|
||||
.select(PmsSku::getStockNum));
|
||||
Integer stockNum = pmsSku != null ? pmsSku.getStockNum() : 0;
|
||||
return stockNum;
|
||||
public SkuInfoDTO getSkuInfo(Long skuId) {
|
||||
return this.baseMapper.getSkuInfo(skuId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定库存
|
||||
* 获取商品库存信息列表
|
||||
*
|
||||
* @param lockStock 订单编号 + 锁定商品集合
|
||||
* @param skuIds SKU ID 列表
|
||||
* @return 商品库存信息列表
|
||||
*/
|
||||
@Override
|
||||
public List<SkuInfoDTO> listSkuInfos(List<Long> skuIds) {
|
||||
List<PmsSku> list = this.list(new LambdaQueryWrapper<PmsSku>().in(PmsSku::getId, skuIds));
|
||||
return skuConverter.entity2SkuInfoDto(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并锁定库存
|
||||
*
|
||||
* @param orderToken 订单临时编号 (此时订单未创建)
|
||||
* @param lockedSkuList 锁定商品库存信息列表
|
||||
* @return true/false
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean lockStock(LockStockDTO lockStock) {
|
||||
String orderSn = lockStock.getOrderSn();
|
||||
log.info("创建订单【{}】锁定商品库存:{}", orderSn, lockStock);
|
||||
public boolean lockStock(String orderToken, List<LockedSkuDTO> lockedSkuList) {
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(lockedSkuList), "订单({})未包含任何商品", orderToken);
|
||||
|
||||
List<LockStockDTO.LockedSku> lockedSkus = lockStock.getLockedSkus();
|
||||
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(lockedSkus), "锁定的商品为空");
|
||||
|
||||
// 循环遍历锁定商品
|
||||
for (LockStockDTO.LockedSku lockedSku : lockedSkus) {
|
||||
// 获取商品分布式锁
|
||||
RLock lock = redissonClient.getLock(ProductConstants.SKU_LOCK_PREFIX + lockedSku.getSkuId());
|
||||
// 加锁
|
||||
lock.lock();
|
||||
// 校验库存数量是否足够以及锁定库存
|
||||
for (LockedSkuDTO lockedSku : lockedSkuList) {
|
||||
Long skuId = lockedSku.getSkuId();
|
||||
RLock lock = redissonClient.getLock(ProductConstants.SKU_LOCK_PREFIX + skuId); // 构建商品锁对象
|
||||
try {
|
||||
lock.lock();
|
||||
|
||||
Integer quantity = lockedSku.getQuantity(); // 订单的商品数量
|
||||
// 库存足够
|
||||
boolean lockResult = this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.setSql("locked_stock_num = locked_stock_num + " + lockedSku.getCount())
|
||||
.setSql("locked_stock = locked_stock + " + quantity) // 修改锁定商品数
|
||||
.eq(PmsSku::getId, lockedSku.getSkuId())
|
||||
.apply("stock_num - locked_stock_num >= {0}", lockedSku.getCount())
|
||||
.apply("stock - locked_stock >= {0}", quantity) // 剩余商品数 ≥ 订单商品数
|
||||
);
|
||||
Assert.isTrue(lockResult, "锁定商品 {} 失败", lockedSku.getSkuId());
|
||||
Assert.isTrue(lockResult, "商品({})库存不足", lockedSku.getSkuSn());
|
||||
} finally {
|
||||
// 释放锁
|
||||
if (lock.isLocked()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 将锁定的商品和数量缓存至Redis,用于后续的场景:
|
||||
*
|
||||
* 1.订单取消解锁库存;
|
||||
* 2.订单支付扣减库存。
|
||||
*/
|
||||
|
||||
redisTemplate.opsForValue().set(ProductConstants.ORDER_LOCKED_SKUS_PREFIX + orderSn, lockedSkus);
|
||||
|
||||
// 锁定的商品缓存至 Redis (后续使用:1.取消订单解锁库存;2:支付订单扣减库存)
|
||||
redisTemplate.opsForValue().set(ProductConstants.LOCKED_SKUS_PREFIX + orderToken, lockedSkuList);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -107,137 +106,75 @@ public class SkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> implements
|
||||
* <p>
|
||||
* 订单超时未支付,释放锁定的商品库存
|
||||
*
|
||||
* @param orderSn 订单号
|
||||
* @return true/false
|
||||
*/
|
||||
@Override
|
||||
public boolean unlockStock(String orderSn) {
|
||||
log.info("订单取消:释放订单【{}】锁定的商品库存", orderSn);
|
||||
List<LockStockDTO.LockedSku> lockedSkus = (List<LockStockDTO.LockedSku>) redisTemplate.opsForValue()
|
||||
.get(ProductConstants.ORDER_LOCKED_SKUS_PREFIX + orderSn);
|
||||
List<LockedSkuDTO> lockedSkus = (List<LockedSkuDTO>) redisTemplate.opsForValue().get(ProductConstants.LOCKED_SKUS_PREFIX + orderSn);
|
||||
log.info("释放订单({})锁定的商品库存:{}", orderSn, JSONUtil.toJsonStr(lockedSkus));
|
||||
|
||||
// 遍历解锁商品
|
||||
if (CollectionUtil.isNotEmpty(lockedSkus)) {
|
||||
for (LockStockDTO.LockedSku lockedSku : lockedSkus) {
|
||||
// 获取商品分布式锁
|
||||
RLock lock = redissonClient.getLock(ProductConstants.SKU_LOCK_PREFIX + lockedSku.getSkuId());
|
||||
// 加锁
|
||||
// 库存已释放
|
||||
if (CollectionUtil.isEmpty(lockedSkus)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 遍历恢复锁定的商品库存
|
||||
for (LockedSkuDTO lockedSku : lockedSkus) {
|
||||
RLock lock = redissonClient.getLock(ProductConstants.SKU_LOCK_PREFIX + lockedSku.getSkuId()); // 获取商品分布式锁
|
||||
try {
|
||||
lock.lock();
|
||||
try {
|
||||
this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, lockedSku.getSkuId())
|
||||
.setSql("locked_stock_num = locked_stock_num - " + lockedSku.getCount()));
|
||||
} finally {
|
||||
// 释放锁
|
||||
if (lock.isLocked()) {
|
||||
lock.unlock();
|
||||
}
|
||||
this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.setSql("locked_stock = locked_stock - " + lockedSku.getQuantity())
|
||||
.eq(PmsSku::getId, lockedSku.getSkuId())
|
||||
);
|
||||
} finally {
|
||||
if (lock.isLocked()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除订单的商品信息缓存
|
||||
redisTemplate.delete(ProductConstants.ORDER_LOCKED_SKUS_PREFIX + orderSn);
|
||||
// 移除 redis 订单锁定的商品
|
||||
redisTemplate.delete(ProductConstants.LOCKED_SKUS_PREFIX + orderSn);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扣减库存
|
||||
* <p>
|
||||
* 订单支付完成扣减订单商品库存和释放锁定的库存
|
||||
* 订单支付扣减商品库存和释放锁定库存
|
||||
*
|
||||
* @param orderSn 订单编号
|
||||
* @return ture/false
|
||||
*/
|
||||
@Override
|
||||
public boolean deductStock(String orderSn) {
|
||||
log.info("订单【{}】支付成功:扣减订单商品库存", orderSn);
|
||||
|
||||
// 获取订单提交时锁定的商品
|
||||
List<LockStockDTO.LockedSku> lockedSkus = (List<LockStockDTO.LockedSku>) redisTemplate.opsForValue()
|
||||
.get(ProductConstants.ORDER_LOCKED_SKUS_PREFIX + orderSn);
|
||||
List<LockedSkuDTO> lockedSkus = (List<LockedSkuDTO>) redisTemplate.opsForValue().get(ProductConstants.LOCKED_SKUS_PREFIX + orderSn);
|
||||
log.info("订单({})支付成功,扣减订单商品库存:{}", orderSn, JSONUtil.toJsonStr(lockedSkus));
|
||||
|
||||
if (CollectionUtil.isNotEmpty(lockedSkus)) {
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(lockedSkus), "扣减商品库存失败:订单({})未包含商品");
|
||||
|
||||
for (LockStockDTO.LockedSku lockedSku : lockedSkus) {
|
||||
// 获取商品分布式锁
|
||||
RLock lock = redissonClient.getLock(ProductConstants.SKU_LOCK_PREFIX + lockedSku.getSkuId());
|
||||
// 加锁
|
||||
for (LockedSkuDTO lockedSku : lockedSkus) {
|
||||
|
||||
RLock lock = redissonClient.getLock(ProductConstants.SKU_LOCK_PREFIX + lockedSku.getSkuId()); // 获取商品分布式锁
|
||||
|
||||
try {
|
||||
lock.lock();
|
||||
try {
|
||||
this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, lockedSku.getSkuId())
|
||||
.setSql("stock_num = stock_num - " + lockedSku.getCount())
|
||||
.setSql("locked_stock_num = locked_stock_num - " + lockedSku.getCount())
|
||||
);
|
||||
} finally {
|
||||
// 释放锁
|
||||
if (lock.isLocked()) {
|
||||
lock.unlock();
|
||||
}
|
||||
this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.setSql("stock = stock - " + lockedSku.getQuantity())
|
||||
.setSql("locked_stock = locked_stock - " + lockedSku.getQuantity())
|
||||
.eq(PmsSku::getId, lockedSku.getSkuId())
|
||||
);
|
||||
} finally {
|
||||
if (lock.isLocked()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 移除订单的商品缓存
|
||||
redisTemplate.delete(ProductConstants.ORDER_LOCKED_SKUS_PREFIX + orderSn);
|
||||
|
||||
// 移除订单锁定的商品
|
||||
redisTemplate.delete(ProductConstants.LOCKED_SKUS_PREFIX + orderSn);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单商品验价
|
||||
* <p>
|
||||
* 判断订单总金额和订单商品实时价格之和是否相等
|
||||
* 不相等:商品实时价格和页面价格不等,订单无效
|
||||
*
|
||||
* @param checkPrice 验价参数(订单总金额、订单商品明细)
|
||||
* @return true/false
|
||||
*/
|
||||
@Override
|
||||
public boolean checkPrice(CheckPriceDTO checkPrice) {
|
||||
log.info("订单【{}】商品验价:{}", checkPrice);
|
||||
// 订单总金额
|
||||
Long orderTotalAmount = checkPrice.getOrderTotalAmount();
|
||||
// 计算商品总金额
|
||||
List<CheckPriceDTO.OrderSku> orderSkus = checkPrice.getOrderSkus();
|
||||
if (orderTotalAmount == null || CollectionUtil.isEmpty(orderSkus)) {
|
||||
log.warn("订单【{}】验价失败:订单总金额或订单商品为空,无法进行验价!");
|
||||
return false;
|
||||
}
|
||||
// 订单商品ID集合
|
||||
List<Long> orderSkuIds = orderSkus.stream().map(orderItem -> orderItem.getSkuId())
|
||||
.collect(Collectors.toList());
|
||||
// 获取商品的实时价格
|
||||
List<PmsSku> skuList = this.list(new LambdaQueryWrapper<PmsSku>().in(PmsSku::getId, orderSkuIds)
|
||||
.select(PmsSku::getId, PmsSku::getPrice)
|
||||
);
|
||||
if (CollectionUtil.isEmpty(skuList)) {
|
||||
log.warn("订单【{}】验价失败:订单商品库存不存在或已下架!");
|
||||
return false;
|
||||
}
|
||||
// 计算商品实时总价
|
||||
Long skuTotalAmount = skuList.stream().map(sku -> {
|
||||
// 获取订单中该商品数量
|
||||
CheckPriceDTO.OrderSku matchOrderSku = orderSkus.stream()
|
||||
.filter(orderSku -> sku.getId().equals(orderSku.getSkuId()))
|
||||
.findFirst().orElse(null);
|
||||
// 单个商品实时总价 = 实时单价 * 订单该商品数量
|
||||
return matchOrderSku != null ? sku.getPrice() * matchOrderSku.getCount() : 0L;
|
||||
}).reduce(0L, Long::sum);
|
||||
// 比较订单总价和商品实时总价
|
||||
boolean checkPriceResult = orderTotalAmount.compareTo(skuTotalAmount) == 0;
|
||||
return checkPriceResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品库存信息
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public SkuDTO getSkuInfo(Long skuId) {
|
||||
return this.baseMapper.getSkuInfo(skuId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.youlai.mall.pms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.pms.model.entity.PmsSpuAttribute;
|
||||
import com.youlai.mall.pms.mapper.PmsSpuAttributeMapper;
|
||||
import com.youlai.mall.pms.model.entity.PmsSpuAttribute;
|
||||
import com.youlai.mall.pms.service.SpuAttributeService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -10,16 +10,16 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.security.util.SecurityUtils;
|
||||
import com.youlai.common.constant.ProductConstants;
|
||||
import com.youlai.mall.pms.enums.AttributeTypeEnum;
|
||||
import com.youlai.mall.pms.common.constant.ProductConstants;
|
||||
import com.youlai.mall.pms.common.enums.AttributeTypeEnum;
|
||||
import com.youlai.mall.pms.converter.SpuAttributeConverter;
|
||||
import com.youlai.mall.pms.converter.SpuConverter;
|
||||
import com.youlai.mall.pms.mapper.PmsSpuMapper;
|
||||
import com.youlai.mall.pms.model.form.PmsSpuAttributeForm;
|
||||
import com.youlai.mall.pms.model.form.PmsSpuForm;
|
||||
import com.youlai.mall.pms.model.entity.PmsSku;
|
||||
import com.youlai.mall.pms.model.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.model.entity.PmsSpuAttribute;
|
||||
import com.youlai.mall.pms.model.form.PmsSpuAttributeForm;
|
||||
import com.youlai.mall.pms.model.form.PmsSpuForm;
|
||||
import com.youlai.mall.pms.model.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.model.vo.*;
|
||||
import com.youlai.mall.pms.service.SkuService;
|
||||
@ -38,7 +38,7 @@ import java.util.stream.Collectors;
|
||||
* 商品业务实现类
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @since 2021/8/8
|
||||
* @date 2021/8/8
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ -59,9 +59,9 @@ public class SpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> implements
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IPage<PmsSpuPageVO> listPmsSpuPages(SpuPageQuery queryParams) {
|
||||
public IPage<PmsSpuPageVO> getSpuPage(SpuPageQuery queryParams) {
|
||||
Page<PmsSpuPageVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<PmsSpuPageVO> list = this.baseMapper.listPmsSpuPages(page, queryParams);
|
||||
List<PmsSpuPageVO> list = this.baseMapper.getSpuPage(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
}
|
||||
@ -73,9 +73,9 @@ public class SpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> implements
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IPage<SpuPageVO> listSpuPages(SpuPageQuery queryParams) {
|
||||
public IPage<SpuPageVO> getSpuPageForApp(SpuPageQuery queryParams) {
|
||||
Page<SpuPageVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<SpuPageVO> list = this.baseMapper.listSpuPages(page, queryParams);
|
||||
List<SpuPageVO> list = this.baseMapper.getSpuPageForApp(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
}
|
||||
|
@ -11,22 +11,17 @@ spring:
|
||||
nacos:
|
||||
# 注册中心
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
server-addr: http://192.168.179.21:8848
|
||||
# 配置中心
|
||||
config:
|
||||
# 本地启动
|
||||
## server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
# 极速启动
|
||||
server-addr: http://localhost:8848
|
||||
server-addr: http://192.168.179.21:8848
|
||||
file-extension: yaml
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
||||
username: nacos
|
||||
password: nacos
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
</select>
|
||||
|
||||
<!-- 获取商品库存单元信息 -->
|
||||
<select id="getSkuInfo" resultType="com.youlai.mall.pms.model.dto.SkuDTO">
|
||||
<select id="getSkuInfo" resultType="com.youlai.mall.pms.model.dto.SkuInfoDTO">
|
||||
select
|
||||
t1.id skuId,
|
||||
t1.sku_sn,
|
||||
|
@ -11,18 +11,14 @@ spring:
|
||||
nacos:
|
||||
# 注册中心
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
server-addr: http://192.168.179.21:8848
|
||||
# 配置中心
|
||||
config:
|
||||
# 本地启动
|
||||
## server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
# 极速启动
|
||||
server-addr: http://localhost:8848
|
||||
server-addr: http://192.168.179.21:8848
|
||||
file-extension: yaml
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
||||
username: nacos
|
||||
password: nacos
|
||||
refresh: true
|
@ -9,15 +9,11 @@ spring:
|
||||
nacos:
|
||||
# 注册中心
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
server-addr: http://192.168.179.21:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: http://localhost:8848
|
||||
server-addr: http://192.168.179.21:8848
|
||||
file-extension: yaml
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
||||
username: nacos
|
||||
password: nacos
|
||||
|
15
pom.xml
15
pom.xml
@ -49,7 +49,7 @@
|
||||
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
||||
|
||||
<!-- api doc -->
|
||||
<knife4j.version>4.1.0</knife4j.version>
|
||||
<knife4j.version>4.3.0</knife4j.version>
|
||||
<swagger.version>2.1.0</swagger.version>
|
||||
|
||||
<!-- tools -->
|
||||
@ -57,7 +57,6 @@
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<weixin-java.version>4.1.5.B</weixin-java.version>
|
||||
<easyexcel.version>3.0.5</easyexcel.version>
|
||||
<easy-captcha.version>1.6.2</easy-captcha.version>
|
||||
<nimbus-jose-jwt.version>9.16.1</nimbus-jose-jwt.version>
|
||||
<thumbnailator.version>0.4.19</thumbnailator.version>
|
||||
|
||||
@ -222,12 +221,6 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.whvcse</groupId>
|
||||
<artifactId>easy-captcha</artifactId>
|
||||
<version>${easy-captcha.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||
@ -276,12 +269,6 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.youlai.auth.authentication.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -81,13 +82,16 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||
}
|
||||
|
||||
// 证码校验
|
||||
// 验证码校验
|
||||
Map<String, Object> additionalParameters = captchaAuthenticationToken.getAdditionalParameters();
|
||||
String verifyCode = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE);
|
||||
String verifyCodeKey = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE_KEY);
|
||||
|
||||
String cacheCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_KEY_PREFIX + verifyCodeKey);
|
||||
if (!StrUtil.equals(verifyCode, cacheCode)) {
|
||||
String cacheCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_CACHE_KEY_PREFIX + verifyCodeKey);
|
||||
|
||||
// 验证码比对
|
||||
MathGenerator mathGenerator = new MathGenerator();
|
||||
if (!mathGenerator.verify(cacheCode, verifyCode)) {
|
||||
throw new OAuth2AuthenticationException("验证码错误");
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package com.youlai.auth.authentication.miniapp;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.youlai.auth.userdetails.member.MemberDetailsService;
|
||||
import com.youlai.auth.service.MemberDetailsService;
|
||||
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
|
@ -2,7 +2,7 @@ package com.youlai.auth.authentication.smscode;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.auth.userdetails.member.MemberDetailsService;
|
||||
import com.youlai.auth.service.MemberDetailsService;
|
||||
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
|
||||
import com.youlai.common.constant.SecurityConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -22,9 +22,9 @@ import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationProvider;
|
||||
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken;
|
||||
import com.youlai.auth.handler.MyAuthenticationFailureHandler;
|
||||
import com.youlai.auth.handler.MyAuthenticationSuccessHandler;
|
||||
import com.youlai.auth.userdetails.member.MemberDetailsService;
|
||||
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||
import com.youlai.auth.userdetails.user.jackson.SysUserMixin;
|
||||
import com.youlai.auth.service.MemberDetailsService;
|
||||
import com.youlai.auth.model.SysUserDetails;
|
||||
import com.youlai.auth.jackson.SysUserMixin;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -245,8 +245,6 @@ public class AuthorizationServerConfig {
|
||||
|
||||
/**
|
||||
* 初始化创建商城管理客户端
|
||||
*
|
||||
* @param registeredClientRepository
|
||||
*/
|
||||
private void initMallAdminClient(JdbcRegisteredClientRepository registeredClientRepository) {
|
||||
|
||||
@ -254,7 +252,6 @@ public class AuthorizationServerConfig {
|
||||
String clientSecret = "123456";
|
||||
String clientName = "商城管理客户端";
|
||||
|
||||
|
||||
/*
|
||||
如果使用明文,客户端认证时会自动升级加密方式,换句话说直接修改客户端密码,所以直接使用 bcrypt 加密避免不必要的麻烦
|
||||
官方ISSUE: https://github.com/spring-projects/spring-authorization-server/issues/1099
|
||||
@ -286,8 +283,6 @@ public class AuthorizationServerConfig {
|
||||
|
||||
/**
|
||||
* 初始化创建商城APP客户端
|
||||
*
|
||||
* @param registeredClientRepository
|
||||
*/
|
||||
private void initMallAppClient(JdbcRegisteredClientRepository registeredClientRepository) {
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
package com.youlai.auth.config;
|
||||
|
||||
import com.youlai.auth.userdetails.member.MemberDetails;
|
||||
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||
import com.youlai.auth.model.MemberDetails;
|
||||
import com.youlai.auth.model.SysUserDetails;
|
||||
import com.youlai.common.constant.SecurityConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
@ -20,7 +21,7 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT 自定义字段
|
||||
* JWT 自定义字段配置
|
||||
*
|
||||
* @author haoxr
|
||||
* @see <a href="https://github.com/spring-projects/spring-authorization-server/pull/1264">How-to: Authorize an access token containing custom authorities</a>
|
||||
@ -32,6 +33,9 @@ public class JwtTokenClaimsConfig {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* JWT 自定义字段
|
||||
*/
|
||||
@Bean
|
||||
public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
|
||||
return context -> {
|
||||
@ -42,7 +46,14 @@ public class JwtTokenClaimsConfig {
|
||||
if (principal instanceof SysUserDetails userDetails) { // 系统用户添加自定义字段
|
||||
|
||||
Long userId = userDetails.getUserId();
|
||||
claims.claim("user_id", userId);
|
||||
claims.claim("userId", userDetails.getUserId());
|
||||
claims.claim("username", userDetails.getUsername());
|
||||
claims.claim("deptId", userDetails.getDeptId());
|
||||
claims.claim("dataScope", userDetails.getDataScope());
|
||||
|
||||
Set<String> roles = userDetails.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
|
||||
claims.claim("authorities", roles);
|
||||
|
||||
// 这里存入角色至JWT,解析JWT的角色用于鉴权的位置: ResourceServerConfig#jwtAuthenticationConverter
|
||||
var authorities = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities())
|
||||
@ -52,7 +63,7 @@ public class JwtTokenClaimsConfig {
|
||||
|
||||
// 权限数据比较多,缓存至redis
|
||||
Set<String> perms = userDetails.getPerms();
|
||||
redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId, perms);
|
||||
redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_KEY_PREFIX + userId, perms);
|
||||
|
||||
} else if (principal instanceof MemberDetails userDetails) { // 商城会员添加自定义字段
|
||||
claims.claim("member_id", String.valueOf(userDetails.getId()));
|
||||
|
@ -1,15 +0,0 @@
|
||||
package com.youlai.auth.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/6/29
|
||||
*/
|
||||
|
||||
@RestController
|
||||
public class AuthController {
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.youlai.auth.handler;
|
||||
|
||||
import com.youlai.common.constant.SecurityConstants;
|
||||
import com.youlai.common.result.Result;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@ -33,24 +34,23 @@ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHand
|
||||
* MappingJackson2HttpMessageConverter 是 Spring 框架提供的一个 HTTP 消息转换器,用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换
|
||||
*/
|
||||
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
||||
private Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();
|
||||
private final Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();
|
||||
|
||||
|
||||
/**
|
||||
* 自定义认证成功响应数据结构
|
||||
*
|
||||
* @param request the request which caused the successful authentication
|
||||
* @param response the response
|
||||
* @param request the request which caused the successful authentication
|
||||
* @param response the response
|
||||
* @param authentication the <tt>Authentication</tt> object which was created during
|
||||
* the authentication process.
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
* the authentication process.
|
||||
* @throws IOException if an I/O related error occurs during the response
|
||||
* @throws ServletException if a servlet-related error occurs during the response
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||
(OAuth2AccessTokenAuthenticationToken) authentication;
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
|
||||
|
||||
OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
|
||||
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
|
||||
@ -69,11 +69,17 @@ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHand
|
||||
builder.additionalParameters(additionalParameters);
|
||||
}
|
||||
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
|
||||
|
||||
Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
|
||||
.convert(accessTokenResponse);
|
||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||
|
||||
this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
|
||||
String clientId = accessTokenAuthentication.getRegisteredClient().getClientId();
|
||||
if (SecurityConstants.KNIFE4J_TEST_CLIENT_ID.equals(clientId)) {
|
||||
// Knife4j测试客户端ID(Knife4j自动填充的 access_token 须原生返回,不能被包装成业务码数据格式)
|
||||
this.accessTokenHttpResponseConverter.write(tokenResponseParameters, null, httpResponse);
|
||||
} else {
|
||||
this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
package com.youlai.auth.userdetails.user.jackson;
|
||||
package com.youlai.auth.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.MissingNode;
|
||||
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||
import com.youlai.auth.model.SysUserDetails;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.auth.userdetails.user.jackson;
|
||||
package com.youlai.auth.jackson;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.auth.userdetails.member;
|
||||
package com.youlai.auth.model;
|
||||
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.mall.ums.dto.MemberAuthDTO;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user