mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-22 20:54:26 +08:00
feat:超时关单和释放库存完善
This commit is contained in:
parent
8288c0ddfe
commit
bc2b3881cd
@ -14,32 +14,18 @@ import javax.validation.constraints.Size;
|
||||
@Data
|
||||
public class OrderSubmitInfoDTO {
|
||||
|
||||
/**
|
||||
* 用户选择地址id
|
||||
*/
|
||||
@NotBlank(message = "请选择收货地址")
|
||||
private String addressId;
|
||||
|
||||
/**
|
||||
* 如果携带skuId则表示该订单通过直接下单方式生成
|
||||
* 否则从购物车中生成 -- 清空购物车
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 直接下单时商品数量
|
||||
*/
|
||||
private Integer skuNum;
|
||||
|
||||
/**
|
||||
* 优惠券id
|
||||
*/
|
||||
private Long payAmount;
|
||||
|
||||
private String couponId;
|
||||
|
||||
@NotBlank(message = "请选择收货地址")
|
||||
private String addressId;
|
||||
|
||||
@Size(max = 500, message = "订单备注长度不能超过500")
|
||||
private String remark;
|
||||
|
||||
|
||||
private Long payAmount;
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import java.util.List;
|
||||
|
||||
public class OrderConfirmVO extends BaseVO {
|
||||
|
||||
|
||||
/**
|
||||
* 订单总额
|
||||
*/
|
||||
@ -21,28 +20,12 @@ public class OrderConfirmVO extends BaseVO {
|
||||
|
||||
|
||||
/**
|
||||
* 商品列表
|
||||
* 订单商品
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private List<OrderItemVO> items;
|
||||
|
||||
// 优惠券信息
|
||||
@Getter
|
||||
@Setter
|
||||
private List<CouponVO> coupons;
|
||||
|
||||
// 积分信息
|
||||
@Getter
|
||||
@Setter
|
||||
private Integer integration;
|
||||
|
||||
|
||||
/**
|
||||
* 应付价格
|
||||
*/
|
||||
@Setter
|
||||
private Long payAmount;
|
||||
|
||||
public Long getTotalPrice() {
|
||||
Long total = 0L;
|
||||
@ -51,12 +34,4 @@ public class OrderConfirmVO extends BaseVO {
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public Long getPayPrice() {
|
||||
Long total = 0L;
|
||||
if (items != null && items.size() > 0) {
|
||||
total = items.stream().mapToLong(OrderItemVO::getSubtotal).sum();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +55,6 @@
|
||||
<artifactId>common-rabbitmq</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
|
@ -0,0 +1,81 @@
|
||||
package com.youlai.mall.oms.config;
|
||||
|
||||
import com.youlai.mall.oms.constant.OmsConstants;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/17
|
||||
* @desc rabbitmq 业务相关配置类
|
||||
* 订单相关业务统一使用 order-event-exchange 交换机
|
||||
* 1. 订单创建成功,发送消息到创建订单的路由
|
||||
* 2. 创建订单的路由转发消息给延时队列,延时队列的延时时间就是订单从创建到支付过程,允许的最大等待时间。延时队列不能有消费者(即消息不能被消费)
|
||||
* 3. 延时时间一到,消息被转入DLX(死信路由)
|
||||
* 4. 死信路由把死信消息转发给死信队列
|
||||
* 5. 订单系统监听死信队列,获取到死信消息后,执行关单解库存操作
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class RabbitMQConfig {
|
||||
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
/**
|
||||
* 定义交换机
|
||||
*/
|
||||
@Bean
|
||||
public Exchange exchange() {
|
||||
return new TopicExchange("order_event_exchange", true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延时队列
|
||||
*/
|
||||
@Bean
|
||||
public Queue delayQueue() {
|
||||
// 延时队列的消息过期了,会自动触发消息的转发,根据routingKey发送到指定的exchange中,exchange路由到死信队列
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("x-dead-letter-exchange", "order_event_exchange");
|
||||
args.put("x-dead-letter-routing-key", "order:close"); // 死信路由Key
|
||||
args.put("x-message-ttl", 60000); // 单位:毫秒,配置1分钟测试使用
|
||||
return new Queue("order.delay.queue", true, false, false, args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 延时队列绑定交换机,路由键order.create
|
||||
* 订单提交时会发送routingKey=order.create.order的消息至exchange,然后会被路由到上面的delayQueue延时队列,延时队列没有消费者,到期后会降消息转发
|
||||
*/
|
||||
@Bean
|
||||
public Binding delayQueueBinding() {
|
||||
return new Binding("order.delay.queue", Binding.DestinationType.QUEUE,"order_event_exchange","order.create",null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 死信队列(普通队列)
|
||||
*/
|
||||
@Bean
|
||||
public Queue closeOrderQueue() {
|
||||
return new Queue("order.close.queue", true, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 死信队列绑定交换机
|
||||
* 其中死信路由的routingKey=order:close和延时队列的routingKey一致,延时队列过期时将消息发送给exchange,exchange再路由到死信队列
|
||||
*/
|
||||
@Bean
|
||||
public Binding closeOrderQueueBinding() {
|
||||
return new Binding("order.close.queue", Binding.DestinationType.QUEUE,"order_event_exchange","order:close",null);
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,6 @@ import com.google.common.collect.Lists;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.OAuthBuilder;
|
||||
|
@ -1,145 +0,0 @@
|
||||
package com.youlai.mall.oms.config.rabbitmq;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.CorrelationData;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @desc rabbitmq 业务相关配置类
|
||||
* 订单相关业务统一使用 order-event-exchange 交换机
|
||||
* order.delay.queue 队列,用于存放需要延时多长时间处理的订单,不需要被消费
|
||||
* order.release.queue 队列,用于真正执行处理订单,需要被业务消费,如果订单还没有付款需要自动关闭订单并释放库存
|
||||
* <p>
|
||||
* order.create.order 建立交换机与 order.delay.queue 队列的绑定关系,目的是将新创建的订单放入延时队列中
|
||||
* order.release.order 建立交换机与 order.release.queue 队列的绑定关系,目的是将延时队列中到达延时时间的订单执行释放操作
|
||||
* <p>
|
||||
* 自动创建相关 交换机,队列,路由键
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/17
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class OmsRabbitConfig {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
/**
|
||||
* 订单死信-延时队列
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Queue orderDelayQueue() {
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("x-dead-letter-exchange", OmsRabbitConstants.ORDER_EVENT_EXCHANGE);
|
||||
args.put("x-dead-letter-routing-key", OmsRabbitConstants.ORDER_RELEASE_ORDER_KEY);
|
||||
args.put("x-message-ttl", 60000);
|
||||
Queue queue = new Queue(OmsRabbitConstants.ORDER_DELAY_QUEUE, true, false, false, args);
|
||||
|
||||
return queue;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放订单队列
|
||||
* 订单系统从该队列中取出订单order,判断是否超时未支付
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Queue orderReleaseQueue() {
|
||||
return new Queue(OmsRabbitConstants.ORDER_RELEASE_QUEUE, true, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Topic类型 订单交换机
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Exchange orderEventExchange() {
|
||||
return new TopicExchange(OmsRabbitConstants.ORDER_EVENT_EXCHANGE, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立 order.delay.queue 队列与交换机绑定
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Binding orderCreateOrderBinding() {
|
||||
//String destination, DestinationType destinationType, String exchange, String routingKey,
|
||||
// @Nullable Map<String, Object> arguments
|
||||
return new Binding(OmsRabbitConstants.ORDER_DELAY_QUEUE, Binding.DestinationType.QUEUE, OmsRabbitConstants.ORDER_EVENT_EXCHANGE, OmsRabbitConstants.ORDER_CREATE_ORDER_KEY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立 order.release.queue 与交换机绑定
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Binding orderReleaseOrderBinding() {
|
||||
return new Binding(OmsRabbitConstants.ORDER_RELEASE_QUEUE, Binding.DestinationType.QUEUE, OmsRabbitConstants.ORDER_EVENT_EXCHANGE, OmsRabbitConstants.ORDER_RELEASE_ORDER_KEY, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 生产者投递消息后,如果Broker收到消息后,会给生产者一个ACK。生产者通过ACK,可以确认这条消息是否正常发送到Broker,这种方式是消息可靠性投递的核心
|
||||
* 步骤1:yaml文件中添加配置 spring.rabbitmq.publisher-confirm-type: correlated
|
||||
* 步骤2:编写代码
|
||||
*/
|
||||
@PostConstruct
|
||||
public void setConfirmCallback() {
|
||||
|
||||
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
|
||||
|
||||
/**
|
||||
* @param correlationData 发送消息时指定的唯一关联数据(消息id)
|
||||
* @param ack 投递结果
|
||||
* @param cause 失败原因
|
||||
*/
|
||||
@Override
|
||||
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
|
||||
if (ack) {
|
||||
log.info("消息投递到交换机成功:[correlationData={}]", correlationData);
|
||||
} else {
|
||||
log.error("消息投递到交换机成功:[correlationData={},原因={}]", correlationData, cause);
|
||||
}
|
||||
|
||||
//TODO 根据ACK状态做对应的消息更新操作
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 注意下面两项必须同时配置
|
||||
* # 开启阶段二(消息从E->Q)的确认回调 Exchange --> Queue returnCallback
|
||||
* spring.rabbitmq.publisher-returns=true
|
||||
*
|
||||
* #为true,则交换机处理消息到路由失败,则会返回给生产者
|
||||
* spring.rabbitmq.template.mandatory=true
|
||||
*/
|
||||
@PostConstruct
|
||||
public void setQueueCallback() {
|
||||
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
|
||||
|
||||
@Override
|
||||
public void returnedMessage(ReturnedMessage returnedMessage) {
|
||||
log.error("路由到队列失败,[消息内容:{},交换机:{},路由件:{},回复码:{},回复文本:{}]",
|
||||
returnedMessage.getMessage(), returnedMessage.getExchange(),
|
||||
returnedMessage.getRoutingKey(), returnedMessage.getReplyCode(), returnedMessage.getReplyText());
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package com.youlai.mall.oms.config.rabbitmq;
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @desc RabbitMQ 相关常量
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/17
|
||||
*/
|
||||
public class OmsRabbitConstants {
|
||||
|
||||
public static final String ORDER_EVENT_EXCHANGE = "order_event_exchange";
|
||||
|
||||
public static final String ORDER_DELAY_QUEUE = "order.delay.queue";
|
||||
|
||||
public static final String ORDER_RELEASE_QUEUE = "order.release.queue";
|
||||
|
||||
public static final String ORDER_CREATE_ORDER_KEY = "order.create.order";
|
||||
|
||||
public static final String ORDER_RELEASE_ORDER_KEY = "order.release.order";
|
||||
|
||||
public static final Integer ORDER_RELEASE_TTL = 60000;
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package com.youlai.mall.oms.common;
|
||||
|
||||
public interface RedisConstants {
|
||||
package com.youlai.mall.oms.constant;
|
||||
|
||||
/**
|
||||
* @author hxr
|
||||
* @date 2021-03-16
|
||||
*/
|
||||
public interface OmsConstants {
|
||||
Long REDIS_KEY_TIME_OUT = 3600 * 24L;
|
||||
|
||||
String CART_KEY = "cart:";
|
||||
@ -9,4 +12,5 @@ public interface RedisConstants {
|
||||
String TOKEN_VERIFY = "token_verify:";
|
||||
|
||||
String BUSINESS_NO_PREFIX = "businessno:";
|
||||
|
||||
}
|
@ -41,8 +41,9 @@ public class OrderPayController {
|
||||
@ApiOperation("订单支付")
|
||||
@PostMapping
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "payType", value = "支付方式", paramType = "query", dataType = "Integer"),
|
||||
@ApiImplicitParam(name = "orderId", value = "订单ID", paramType = "query", dataType = "Long")
|
||||
@ApiImplicitParam(name = "orderId", value = "订单ID", paramType = "query", dataType = "Long"),
|
||||
@ApiImplicitParam(name = "payType", value = "支付方式", paramType = "query", dataType = "Integer")
|
||||
|
||||
})
|
||||
public Result pay(
|
||||
Integer payType,
|
||||
@ -50,14 +51,13 @@ public class OrderPayController {
|
||||
) {
|
||||
PayTypeEnum payTypeEnum = PayTypeEnum.getValue(payType);
|
||||
|
||||
boolean result = false;
|
||||
switch (payTypeEnum) {
|
||||
case ALIPAY:
|
||||
case WEIXIN:
|
||||
// TODO
|
||||
break;
|
||||
case BALANCE:
|
||||
orderPayService.payWithBalance(orderId);
|
||||
orderPayService.payWithBalance(orderId);
|
||||
break;
|
||||
default:
|
||||
return Result.failed("系统暂不支持该支付方式~");
|
||||
|
@ -0,0 +1,56 @@
|
||||
package com.youlai.mall.oms.listener;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.youlai.mall.oms.service.IOrderService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* @author hxr
|
||||
* @date 2021-03-16
|
||||
*/
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class RabbitMQListener {
|
||||
|
||||
IOrderService orderService;
|
||||
|
||||
RabbitTemplate rabbitTemplate;
|
||||
|
||||
/**
|
||||
* 订单超时未支付,关闭订单,释放库存
|
||||
* @param orderSn
|
||||
* @param message
|
||||
* @param channel
|
||||
*/
|
||||
@RabbitListener(queues = "order.close.queue")
|
||||
public void closeOrder(String orderSn, Message message, Channel channel) {
|
||||
try {
|
||||
|
||||
if (orderService.closeOrder(orderSn)) {
|
||||
// 如果关单成功,发送消息释放库存
|
||||
rabbitTemplate.convertAndSend("product_event_change", "inventory:unlock", orderSn);
|
||||
} else {
|
||||
// 如果关单失败,则订单可能已经被处理,直接手动ACK确认消息
|
||||
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 消费失败后,重新入队
|
||||
try {
|
||||
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
|
||||
} catch (IOException ioException) {
|
||||
log.error("释放库存失败,orderSn=[{}]", orderSn);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ public interface IOrderService extends IService<OmsOrder> {
|
||||
*
|
||||
* @param orderSn 订单号
|
||||
*/
|
||||
boolean autoCancelOrder(String orderSn);
|
||||
boolean closeOrder(String orderSn);
|
||||
|
||||
/**
|
||||
* 取消订单接口
|
||||
|
@ -17,7 +17,7 @@ import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.youlai.mall.oms.common.RedisConstants.CART_KEY;
|
||||
import static com.youlai.mall.oms.constant.OmsConstants.CART_KEY;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1,15 +1,11 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.RequestUtils;
|
||||
import com.youlai.mall.oms.dao.OrderPayDao;
|
||||
import com.youlai.mall.oms.enums.PayTypeEnum;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.PayTypeEnum;
|
||||
import com.youlai.mall.oms.pojo.domain.OmsOrder;
|
||||
import com.youlai.mall.oms.pojo.domain.OmsOrderPay;
|
||||
import com.youlai.mall.oms.pojo.vo.PayInfoVO;
|
||||
@ -17,13 +13,11 @@ import com.youlai.mall.oms.service.IOrderLogService;
|
||||
import com.youlai.mall.oms.service.IOrderPayService;
|
||||
import com.youlai.mall.oms.service.IOrderService;
|
||||
import com.youlai.mall.ums.api.app.MemberFeignService;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberDTO;
|
||||
import io.seata.spring.annotation.GlobalTransactional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
@ -48,7 +42,7 @@ public class OrderPayServiceImpl extends ServiceImpl<OrderPayDao, OmsOrderPay> i
|
||||
|
||||
// 2. 远程调用查询会员余额
|
||||
Long userId = RequestUtils.getUserId();
|
||||
Long balance = memberFeignService.getUserById(userId).getData().getBalance();
|
||||
Long balance = memberFeignService.getBalance(userId).getData();
|
||||
|
||||
if (Long.compare(balance, order.getPayAmount()) == -1) {
|
||||
throw new BizException("会员余额不足,请先充值");
|
||||
@ -81,26 +75,16 @@ public class OrderPayServiceImpl extends ServiceImpl<OrderPayDao, OmsOrderPay> i
|
||||
|
||||
@Override
|
||||
public PayInfoVO getPayInfo(Long orderId) {
|
||||
Long userId = RequestUtils.getUserId();
|
||||
PayInfoVO payInfoVO = new PayInfoVO();
|
||||
|
||||
// 1、获取订单应支付金额
|
||||
OmsOrder omsOrder = orderService.getByOrderId(orderId);
|
||||
payInfoVO.setPayAmount(omsOrder.getPayAmount());
|
||||
|
||||
// 2、获取会员余额
|
||||
try {
|
||||
Result<MemberDTO> memberInfo = memberFeignService.getUserById(RequestUtils.getUserId());
|
||||
if (memberInfo != null && memberInfo.getCode().equals(ResultCode.SUCCESS.getCode())) {
|
||||
MemberDTO data = memberInfo.getData();
|
||||
if (data != null) {
|
||||
payInfoVO.setBalance(data.getBalance());
|
||||
} else {
|
||||
log.error("获取会员信息失败,userId={}", userId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取会员余额失败,userId={}", userId, e);
|
||||
}
|
||||
Long userId = RequestUtils.getUserId();
|
||||
Long balance = memberFeignService.getBalance(userId).getData();
|
||||
payInfoVO.setBalance(balance);
|
||||
|
||||
return payInfoVO;
|
||||
}
|
||||
|
@ -1,74 +0,0 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.mall.oms.config.rabbitmq.OmsRabbitConstants;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.pojo.domain.OmsOrder;
|
||||
import com.youlai.mall.oms.pojo.domain.OmsOrderItem;
|
||||
import com.youlai.mall.oms.service.IOrderItemService;
|
||||
import com.youlai.mall.oms.service.OrderRabbitService;
|
||||
import com.youlai.mall.oms.service.IOrderService;
|
||||
import com.youlai.mall.pms.api.app.InventoryFeignService;
|
||||
import com.youlai.mall.pms.pojo.dto.InventoryDTO;
|
||||
import io.seata.spring.annotation.GlobalTransactional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RabbitListener(queues = OmsRabbitConstants.ORDER_RELEASE_QUEUE)
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class OrderRabbitServiceImpl implements OrderRabbitService {
|
||||
|
||||
private IOrderService orderService;
|
||||
private IOrderItemService orderItemService;
|
||||
private InventoryFeignService inventoryFeignService;
|
||||
|
||||
/**
|
||||
* 接收超时订单消息
|
||||
* 订单已支付 - 确认接收消息
|
||||
* 订单未支付 - 关闭订单,释放库存
|
||||
* 出现异常,消费消息失败,将数据重新放入队列,等待下次消费
|
||||
*
|
||||
* @param orderSn
|
||||
*/
|
||||
@Override
|
||||
@RabbitHandler
|
||||
@GlobalTransactional(rollbackFor = Exception.class)
|
||||
public void releaseOrder(String orderSn, Message message, Channel channel) {
|
||||
long msgTag = message.getMessageProperties().getDeliveryTag();
|
||||
log.info("获取到消息,msgTag={},message={},body={}", msgTag, message.toString(), orderSn);
|
||||
try {
|
||||
OmsOrder order = orderService.getOne(new LambdaQueryWrapper<OmsOrder>()
|
||||
.eq(OmsOrder::getOrderSn, orderSn));
|
||||
if (order.getStatus().equals(OrderStatusEnum.PENDING_PAYMENT.getCode())) { // 待支付订单超时未支付系统自动取消
|
||||
if (orderService.autoCancelOrder(orderSn)) {
|
||||
List<OmsOrderItem> orderItems = orderItemService.getByOrderId(order.getId());
|
||||
List<InventoryDTO> items = orderItems.stream().map(item -> InventoryDTO.builder()
|
||||
.skuId(item.getSkuId())
|
||||
.num(item.getSkuQuantity()).build())
|
||||
.collect(Collectors.toList());
|
||||
Result result = inventoryFeignService.unlockInventory(items);
|
||||
if (!StrUtil.equals(result.getCode(), ResultCode.SUCCESS.getCode())) {
|
||||
throw new BizException("关闭订单失败,释放库存错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
channel.basicAck(msgTag, false);
|
||||
} catch (Exception e) {
|
||||
throw new BizException("关闭订单失败,orderSn=" + orderSn);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.BeanMapperUtils;
|
||||
import com.youlai.common.web.util.RequestUtils;
|
||||
import com.youlai.mall.oms.config.rabbitmq.OmsRabbitConstants;
|
||||
import com.youlai.mall.oms.constant.OmsConstants;
|
||||
import com.youlai.mall.oms.dao.OrderDao;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.OrderTypeEnum;
|
||||
@ -114,13 +114,14 @@ public class OrderServiceImpl extends ServiceImpl<OrderDao, OmsOrder> implements
|
||||
threadLocal.set(submitInfoDTO);
|
||||
|
||||
OrderSubmitInfoDTO submitInfo = threadLocal.get();
|
||||
log.info("创建订单实体类,submit:{}", submitInfo);
|
||||
log.info("订单提交信息:{}", submitInfo);
|
||||
OmsOrder order = new OmsOrder();
|
||||
order.setOrderSn(IdWorker.getTimeId())
|
||||
.setRemark(submitInfo.getRemark())
|
||||
.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode())
|
||||
.setSourceType(OrderTypeEnum.APP.getCode())
|
||||
.setUserId(RequestUtils.getUserId());
|
||||
log.debug("完善后的订单信息:{}", order.toString());
|
||||
orderBO.setOrder(order);
|
||||
|
||||
}, executor);
|
||||
@ -131,16 +132,21 @@ public class OrderServiceImpl extends ServiceImpl<OrderDao, OmsOrder> implements
|
||||
orderItemFuture = CompletableFuture.runAsync(() -> {
|
||||
RequestContextHolder.setRequestAttributes(attributes);
|
||||
threadLocal.set(submitInfoDTO);
|
||||
|
||||
log.info("创建订单商品,submitInfoDTO:{}", submitInfoDTO);
|
||||
OrderSubmitInfoDTO submitInfo = threadLocal.get();
|
||||
log.info("创建订单商品,submitInfo:{}", submitInfo);
|
||||
|
||||
List<OmsOrderItem> orderItems;
|
||||
|
||||
log.info("判断结果,{}", submitInfoDTO.getSkuId() != null);
|
||||
if (submitInfoDTO.getSkuId() != null) { // 直接下单
|
||||
log.info("直接下单");
|
||||
orderItems = new ArrayList<>();
|
||||
orderItems.add(OmsOrderItem.builder().skuId(submitInfo.getSkuId()).skuQuantity(submitInfo.getSkuNum()).build());
|
||||
} else { // 购物车下单
|
||||
log.info("准备获取购物车");
|
||||
CartVO cart = cartService.getCart();
|
||||
log.info("购物车:{}", cart.toString());
|
||||
orderItems = cart.getItems().stream().map(cartItem -> OmsOrderItem.builder().skuId(cartItem.getSkuId())
|
||||
.skuQuantity(cartItem.getNum())
|
||||
.build()
|
||||
@ -150,14 +156,15 @@ public class OrderServiceImpl extends ServiceImpl<OrderDao, OmsOrder> implements
|
||||
List<Long> skuIds = orderItems.stream().map(item -> item.getSkuId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<SkuDTO> skus = inventoryFeignService.listBySkuIds(skuIds).getData();
|
||||
if (CollectionUtil.isEmpty(skus)) {
|
||||
List<SkuDTO> skuList = inventoryFeignService.listBySkuIds(skuIds).getData();
|
||||
if (CollectionUtil.isEmpty(skuList)) {
|
||||
throw new BizException("订单商品库存为空");
|
||||
}
|
||||
for (OmsOrderItem orderItem : orderItems) {
|
||||
skus.stream().filter(sku -> sku.getId().equals(orderItem.getSkuId())).findFirst()
|
||||
skuList.stream().filter(sku -> sku.getId().equals(orderItem.getSkuId())).findFirst()
|
||||
.ifPresent(skuItem -> {
|
||||
BeanUtil.copyProperties(skuItem, orderItem);
|
||||
orderItem.setSkuPrice(skuItem.getPrice());
|
||||
orderItem.setSkuTotalPrice(orderItem.getSkuPrice() * orderItem.getSkuQuantity());
|
||||
});
|
||||
}
|
||||
@ -172,8 +179,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderDao, OmsOrder> implements
|
||||
threadLocal.set(submitInfoDTO);
|
||||
|
||||
String addressId = threadLocal.get().getAddressId();
|
||||
log.info("获取订单地址信息,addressId:{}", addressId);
|
||||
UmsAddressDTO userAddress = memberFeignService.getAddressById(addressId).getData();
|
||||
log.debug("获取用户地址:{}", userAddress.toString());
|
||||
if (userAddress == null) {
|
||||
throw new BizException("提交订单失败,无法获取用户地址信息");
|
||||
}
|
||||
@ -262,11 +269,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderDao, OmsOrder> implements
|
||||
cartService.deleteSelectedItem();
|
||||
}
|
||||
|
||||
// 将订单放入定时队列中,超时未支付系统自动关单,释放库存
|
||||
rabbitTemplate.convertAndSend(OmsRabbitConstants.ORDER_EVENT_EXCHANGE,
|
||||
OmsRabbitConstants.ORDER_CREATE_ORDER_KEY,
|
||||
orderBO.getOrder().getOrderSn()
|
||||
);
|
||||
// 将订单放入延时队列,超时未支付系统自动关单
|
||||
rabbitTemplate.convertAndSend("order_event_exchange", "order:create", orderBO.getOrder().getOrderSn());
|
||||
|
||||
// 保存日志
|
||||
orderLogService.addOrderLogs(orderId,
|
||||
@ -283,11 +287,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderDao, OmsOrder> implements
|
||||
|
||||
|
||||
@Override
|
||||
public boolean autoCancelOrder(String orderSn) {
|
||||
log.info("订单超时未支付,系统自动关闭,orderSn={}", orderSn);
|
||||
public boolean closeOrder(String orderSn) {
|
||||
OmsOrder order = this.getOne(new LambdaQueryWrapper<OmsOrder>().eq(OmsOrder::getOrderSn, orderSn));
|
||||
if (!OrderStatusEnum.PENDING_PAYMENT.getCode().equals(order.getStatus())) {
|
||||
throw new BizException("系统自动取消订单失败");
|
||||
return false;
|
||||
}
|
||||
order.setStatus(OrderStatusEnum.AUTO_CANCEL.getCode());
|
||||
this.updateById(order);
|
||||
|
@ -13,8 +13,8 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import static com.youlai.mall.oms.common.RedisConstants.REDIS_KEY_TIME_OUT;
|
||||
import static com.youlai.mall.oms.common.RedisConstants.TOKEN_VERIFY;
|
||||
import static com.youlai.mall.oms.constant.OmsConstants.REDIS_KEY_TIME_OUT;
|
||||
import static com.youlai.mall.oms.constant.OmsConstants.TOKEN_VERIFY;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -26,13 +26,13 @@ public interface InventoryFeignService {
|
||||
/**
|
||||
* 锁定库存
|
||||
*/
|
||||
@PatchMapping("/api.app/v1/skus/batch/lock_inventory")
|
||||
@PutMapping("/api.app/v1/skus/batch/lock_inventory")
|
||||
Result lockInventory(@RequestBody List<InventoryDTO> list);
|
||||
|
||||
/**
|
||||
* 解锁库存
|
||||
*/
|
||||
@PatchMapping("/api.app/v1/skus/batch/unlock_inventory")
|
||||
@PutMapping("/api.app/v1/skus/batch/unlock_inventory")
|
||||
Result<Boolean> unlockInventory(@RequestBody List<InventoryDTO> list);
|
||||
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.youlai.mall.pms.pojo.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @description 库存数量
|
||||
@ -11,6 +13,8 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InventoryDTO {
|
||||
|
||||
@ApiModelProperty("库存ID")
|
||||
|
@ -44,6 +44,11 @@
|
||||
<artifactId>common-elasticsearch</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-rabbitmq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
@ -4,8 +4,8 @@ package com.youlai.mall.pms.common.constant;
|
||||
* @author haoxr
|
||||
* @date 2021-02-28 20:10
|
||||
*/
|
||||
public interface RedisConstants {
|
||||
public interface PmsConstants {
|
||||
|
||||
String PRODUCT_INVENTORY_PREFIX = "product:inventory:";
|
||||
String PRODUCT_INVENTORY_PREFIX = "pms:inventory:";
|
||||
|
||||
}
|
@ -43,7 +43,7 @@ public class SkuController {
|
||||
|
||||
@ApiOperation(value = "锁定库存", httpMethod = "PUT")
|
||||
@ApiImplicitParam(name = "list", value = "锁定库存", required = true, paramType = "body", dataType = "InventoryNumDTO")
|
||||
@PatchMapping("/batch/_lock")
|
||||
@PutMapping("/batch/lock_inventory")
|
||||
public Result<Boolean> lockInventory(@RequestBody List<InventoryDTO> list) {
|
||||
boolean result = iPmsSkuService.lockInventory(list);
|
||||
return Result.judge(result);
|
||||
@ -52,7 +52,7 @@ public class SkuController {
|
||||
|
||||
@ApiOperation(value = "解锁库存", httpMethod = "PUT")
|
||||
@ApiImplicitParam(name = "list", value = "释放库存", required = true, paramType = "body", dataType = "InventoryNumDTO")
|
||||
@PatchMapping("/batch/_unlock")
|
||||
@PutMapping("/batch/unlock_inventory")
|
||||
public Result<Boolean> unlockInventory(@RequestBody List<InventoryDTO> list) {
|
||||
boolean result = iPmsSkuService.unlockInventory(list);
|
||||
return Result.judge(result);
|
||||
|
@ -0,0 +1,20 @@
|
||||
package com.youlai.mall.pms.listener;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author hxr
|
||||
* @date 2021-03-16
|
||||
*/
|
||||
@Component
|
||||
public class InventoryListener {
|
||||
|
||||
|
||||
/**
|
||||
* 支付成功减库存
|
||||
*/
|
||||
public void minusInventory(){
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,7 @@ 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.web.exception.BizException;
|
||||
import com.youlai.mall.pms.common.constant.RedisConstants;
|
||||
import com.youlai.mall.pms.common.constant.PmsConstants;
|
||||
import com.youlai.mall.pms.mapper.PmsSkuMapper;
|
||||
import com.youlai.mall.pms.pojo.domain.PmsSku;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuDTO;
|
||||
@ -75,7 +75,7 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
||||
public Integer getInventoryById(Long id) {
|
||||
Integer inventory = 0;
|
||||
// 读->缓存
|
||||
Object cacheVal = redisTemplate.opsForValue().get(RedisConstants.PRODUCT_INVENTORY_PREFIX + id);
|
||||
Object cacheVal = redisTemplate.opsForValue().get(PmsConstants.PRODUCT_INVENTORY_PREFIX + id);
|
||||
if (cacheVal != null) {
|
||||
inventory = Convert.toInt(cacheVal);
|
||||
return inventory;
|
||||
@ -89,7 +89,7 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
||||
if (pmsSku != null) {
|
||||
inventory = pmsSku.getInventory();
|
||||
// 写->缓存
|
||||
redisTemplate.opsForValue().set(RedisConstants.PRODUCT_INVENTORY_PREFIX + id, inventory);
|
||||
redisTemplate.opsForValue().set(PmsConstants.PRODUCT_INVENTORY_PREFIX + id, inventory);
|
||||
}
|
||||
|
||||
return inventory;
|
||||
|
@ -50,6 +50,13 @@ public interface MemberFeignService {
|
||||
@PutMapping("/api.app/v1/users/{id}/balance")
|
||||
Result updateBalance(@PathVariable Long id, @RequestParam Long balance);
|
||||
|
||||
|
||||
/**
|
||||
* 获取会员余额
|
||||
*/
|
||||
@GetMapping("/api.app/v1/users/{id}/balance")
|
||||
Result<Long> getBalance(@PathVariable Long id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,4 +108,17 @@ public class UserController {
|
||||
boolean result = iUmsUserService.updateById(user);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "获取会员余额", httpMethod = "GET")
|
||||
@ApiImplicitParam(name = "id", value = "会员ID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/{id}/balance")
|
||||
public Result<Long> updateBalance(@PathVariable Long id) {
|
||||
Long balance = 0l;
|
||||
UmsUser user = iUmsUserService.getById(id);
|
||||
if (user != null) {
|
||||
balance = user.getBalance();
|
||||
}
|
||||
return Result.success(balance);
|
||||
}
|
||||
|
||||
}
|
||||
|
1
pom.xml
1
pom.xml
@ -15,7 +15,6 @@
|
||||
<module>youlai-auth</module>
|
||||
<module>youlai-gateway</module>
|
||||
<module>youlai-registry</module>
|
||||
<module>youlai-test</module>
|
||||
<module>mall-sms</module>
|
||||
<module>mall-ums</module>
|
||||
<module>mall-pms</module>
|
||||
|
@ -13,7 +13,6 @@ public class FieldFillHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
log.info("自动填充时间字段");
|
||||
this.setFieldValByName("gmtCreate", new Date(), metaObject);
|
||||
this.setFieldValByName("gmtModified", new Date(), metaObject);
|
||||
}
|
||||
|
@ -5,11 +5,18 @@
|
||||
<!-- 自定义属性 -->
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="default"/>
|
||||
|
||||
<!-- 彩色日志依赖的渲染类 -->
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
||||
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
|
||||
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
|
||||
<!-- 彩色日志格式 -->
|
||||
<property name="LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
|
||||
<!-- 输出到控制台-->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>
|
||||
%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%logger|%thread|%X{traceId}|%m%n
|
||||
<Pattern>${LOG_PATTERN}</Pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
@ -1,169 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>youlai-mall</artifactId>
|
||||
<groupId>com.youlai</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>youlai-test</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>oms-api</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>pms-api</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>ums-api</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-mybatis</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-web</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-rabbitmq</artifactId>
|
||||
<version>${youlai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
|
||||
<exclusions>
|
||||
<!-- 排除依赖 指定版本和服务器端一致 -->
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
@ -1,77 +0,0 @@
|
||||
package com.youlai.test.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.OAuthBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author haoxr
|
||||
* @Date 2021-02-25 15:36
|
||||
* @Version 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2WebMvc
|
||||
@Import(BeanValidatorPluginsConfiguration.class)
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Docket restApi() {
|
||||
//schema
|
||||
List<GrantType> grantTypes=new ArrayList<>();
|
||||
//密码模式
|
||||
String passwordTokenUrl="http://localhost:9999/youlai-auth/oauth/token";
|
||||
ResourceOwnerPasswordCredentialsGrant resourceOwnerPasswordCredentialsGrant=new ResourceOwnerPasswordCredentialsGrant(passwordTokenUrl);
|
||||
grantTypes.add(resourceOwnerPasswordCredentialsGrant);
|
||||
OAuth oAuth=new OAuthBuilder().name("oauth2")
|
||||
.grantTypes(grantTypes).build();
|
||||
//context
|
||||
//scope方位
|
||||
List<AuthorizationScope> scopes=new ArrayList<>();
|
||||
scopes.add(new AuthorizationScope("read","read resources"));
|
||||
scopes.add(new AuthorizationScope("write","write resources"));
|
||||
scopes.add(new AuthorizationScope("reads","read all resources"));
|
||||
scopes.add(new AuthorizationScope("writes","write all resources"));
|
||||
|
||||
SecurityReference securityReference=new SecurityReference("oauth2",scopes.toArray(new AuthorizationScope[]{}));
|
||||
SecurityContext securityContext=new SecurityContext(Lists.newArrayList(securityReference),PathSelectors.ant("/**"));
|
||||
//schemas
|
||||
List<SecurityScheme> securitySchemes=Lists.newArrayList(oAuth);
|
||||
//securyContext
|
||||
List<SecurityContext> securityContexts=Lists.newArrayList(securityContext);
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.youlai.mall.oms.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securityContexts(securityContexts)
|
||||
.securitySchemes(securitySchemes)
|
||||
.apiInfo(apiInfo());
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("测试接口")
|
||||
.description("<div style='font-size:14px;color:red;'>Seata、ElasticSearch、RabbitMQ、Redis测试</div>")
|
||||
.termsOfServiceUrl("https://www.youlai.store")
|
||||
.contact(new Contact("yl", "https://github.com/hxrui", "1490493387@qq.com"))
|
||||
.license("Open Source")
|
||||
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
|
||||
.version("1.0.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package com.youlai.test.seata.controller;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.test.seata.dto.OrderDTO;
|
||||
import com.youlai.test.seata.service.IOrderService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @description 订单提交全局事务一致性测试 1.扣減庫存 2. 减账户余额 3.更改订单状态已完成
|
||||
* @createTime 2021/3/13 11:10
|
||||
*/
|
||||
@Api(tags = "【Seata】订单提交")
|
||||
@RequestMapping("/api.admin/v1/orders")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class OrderController {
|
||||
|
||||
private IOrderService orderService;
|
||||
|
||||
@ApiOperation(value = "订单提交", httpMethod = "POST")
|
||||
@PostMapping
|
||||
public Result submit(OrderDTO order) {
|
||||
boolean status;
|
||||
if (order.getOpenTransaction()) {
|
||||
status = orderService.saveWithGlobalTransactional(order);
|
||||
} else {
|
||||
status = orderService.save(order);
|
||||
}
|
||||
return Result.judge(status);
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package com.youlai.test.seata.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @description TODO
|
||||
* @createTime 2021/3/13 11:31
|
||||
*/
|
||||
@Data
|
||||
public class OrderDTO {
|
||||
|
||||
private Long orderId;
|
||||
private Long userId;
|
||||
private Long skuId;
|
||||
private Integer skuNum;
|
||||
private Long skuPrice;
|
||||
|
||||
/**
|
||||
* 是否开启事务 0-关闭 1-开启
|
||||
*/
|
||||
private Boolean openTransaction;
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package com.youlai.test.seata.service;
|
||||
|
||||
import com.youlai.test.seata.dto.OrderDTO;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @description TODO
|
||||
* @createTime 2021/3/13 11:16
|
||||
*/
|
||||
public interface IOrderService {
|
||||
|
||||
boolean save(OrderDTO order);
|
||||
|
||||
boolean saveWithGlobalTransactional(OrderDTO order);
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.youlai.test.seata.service.impl;
|
||||
|
||||
import com.youlai.mall.oms.api.OrderFeignService;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.pms.api.admin.InventoryFeignService;
|
||||
import com.youlai.mall.ums.api.admin.MemberFeignService;
|
||||
import com.youlai.test.seata.dto.OrderDTO;
|
||||
import com.youlai.test.seata.service.IOrderService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @description TODO
|
||||
* @createTime 2021/3/13 11:16
|
||||
*/
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class OrderServiceImpl implements IOrderService {
|
||||
|
||||
private InventoryFeignService inventoryFeignService;
|
||||
|
||||
private MemberFeignService memberFeignService;
|
||||
|
||||
private OrderFeignService orderFeignService;
|
||||
|
||||
@Override
|
||||
public boolean save(OrderDTO order) {
|
||||
log.info("========================扣减库存::Begin======================");
|
||||
inventoryFeignService.deductInventory(order.getSkuId(), order.getSkuNum());
|
||||
log.info("========================扣减库存::End======================");
|
||||
|
||||
log.info("========================扣减账户余额::Begin======================");
|
||||
memberFeignService.deductBalance(order.getUserId(), order.getSkuNum() * order.getSkuPrice());
|
||||
log.info("========================扣减账户余额::End======================");
|
||||
|
||||
log.info("========================修改订单状态::Begin======================");
|
||||
orderFeignService.updateOrderStatus(order.getOrderId(), OrderStatusEnum.FINISHED.getCode());
|
||||
log.info("========================修改订单状态::End======================");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveWithGlobalTransactional(OrderDTO order) {
|
||||
log.info("========================扣减库存::Begin======================");
|
||||
inventoryFeignService.deductInventory(order.getSkuId(), order.getSkuNum());
|
||||
log.info("========================扣减库存::End======================");
|
||||
|
||||
log.info("========================扣减账户余额::Begin======================");
|
||||
memberFeignService.deductBalance(order.getUserId(), order.getSkuNum() * order.getSkuPrice());
|
||||
log.info("========================扣减账户余额::End======================");
|
||||
|
||||
log.info("========================修改订单状态::Begin======================");
|
||||
orderFeignService.updateOrderStatus(order.getOrderId(), OrderStatusEnum.FINISHED.getCode());
|
||||
log.info("========================修改订单状态::End======================");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
server:
|
||||
port: 7474
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: youlai-test
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
|
@ -1,18 +0,0 @@
|
||||
server:
|
||||
port: 8603
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: mall-oms
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: http://c.youlai.store:8848
|
||||
namespace: prod_namespace_id
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: prod_namespace_id
|
||||
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
Loading…
Reference in New Issue
Block a user