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("订单中心REST接口")
+ .description("订单提交、秒杀接口
")
+ .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();
+ }
+
+}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/rabbitmq/OmsRabbitConfig.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/rabbitmq/OmsRabbitConfig.java
new file mode 100644
index 000000000..b4e410129
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/rabbitmq/OmsRabbitConfig.java
@@ -0,0 +1,145 @@
+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 队列,用于真正执行处理订单,需要被业务消费,如果订单还没有付款需要自动关闭订单并释放库存
+ *
+ * order.create.order 建立交换机与 order.delay.queue 队列的绑定关系,目的是将新创建的订单放入延时队列中
+ * order.release.order 建立交换机与 order.release.queue 队列的绑定关系,目的是将延时队列中到达延时时间的订单执行释放操作
+ *
+ * 自动创建相关 交换机,队列,路由键
+ * @email huawei_code@163.com
+ * @date 2021/1/17
+ */
+@Configuration
+@Slf4j
+public class OmsRabbitConfig {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ /**
+ * 订单死信-延时队列
+ *
+ * @return
+ */
+ @Bean
+ public Queue orderDelayQueue() {
+ Map 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 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());
+
+ }
+ });
+ }
+}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/rabbitmq/OmsRabbitConstants.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/rabbitmq/OmsRabbitConstants.java
new file mode 100644
index 000000000..f111627b9
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/rabbitmq/OmsRabbitConstants.java
@@ -0,0 +1,22 @@
+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;
+}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/thread/AsyncConfig.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/thread/AsyncConfig.java
new file mode 100644
index 000000000..1f919a7f9
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/thread/AsyncConfig.java
@@ -0,0 +1,42 @@
+//package com.youlai.mall.oms.config.thread;
+//
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.core.task.AsyncTaskExecutor;
+//import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+//
+//import java.util.concurrent.ThreadPoolExecutor;
+//
+///**
+// * @author huawei
+// * @desc 异步任务线程配置
+// * @email huawei_code@163.com
+// * @date 2021/1/16
+// */
+//@Configuration
+//@Slf4j
+//public class AsyncConfig {
+//
+// @Bean("asyncTaskExecutor")
+// public AsyncTaskExecutor asyncTaskExecutor(ThreadPoolProperties properties) {
+// log.info("loading asyncTaskExecutor ...");
+// ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+// //配置核心线程数
+// executor.setCorePoolSize(properties.getCorePoolSize());
+// //配置最大线程数
+// executor.setMaxPoolSize(properties.getMaxPoolSize());
+// //配置队列大小
+// executor.setQueueCapacity(properties.getQueueCapacity());
+// executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());
+// //配置线程池中的线程的名称前缀
+// executor.setThreadNamePrefix("async-service-");
+//
+// // 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务
+// // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
+// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+// //执行初始化
+// executor.initialize();
+// return executor;
+// }
+//}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/thread/ThreadPoolProperties.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/thread/ThreadPoolProperties.java
new file mode 100644
index 000000000..8bd7fff92
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/thread/ThreadPoolProperties.java
@@ -0,0 +1,23 @@
+//package com.youlai.mall.oms.config.thread;
+//
+//import lombok.Data;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//
+///**
+// * @author huawei
+// * @desc 线程池配置属性类
+// * @email huawei_code@163.com
+// * @date 2021/1/16
+// */
+//@Data
+//@ConfigurationProperties(prefix = "task.pool")
+//public class ThreadPoolProperties {
+//
+// private Integer corePoolSize;
+//
+// private Integer maxPoolSize;
+//
+// private Integer queueCapacity;
+//
+// private Integer keepAliveSeconds;
+//}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/token/TokenVerify.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/token/TokenVerify.java
new file mode 100644
index 000000000..003c3b26d
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/token/TokenVerify.java
@@ -0,0 +1,31 @@
+package com.youlai.mall.oms.config.token;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author huawei
+ * @desc Token 幂等性校验接口注解
+ * 幂等性方案:注解 + 拦截器
+ * 注解:@TokenVerify 注解,该注解有两个属性,generate 和 clean
+ * generate:属性为true是表示请求该方法会生成一个uuid token,同时放入redis中和用户请求中,用于用户下一次请求校验,默认为false
+ * clean:如果属性为true表示请求该方法需要校验token唯一性,并且在校验通过后清除redis中的token
+ * 拦截器:TokenVerifyHandler
+ * 实现原理:拦截所有的请求,如果该请求被 @TokenVerify 注解标识:首先判断该注解属性 generate 是否为true,然后校验属性 clean 是否为true
+ * 最后,在 WebMvcConfig 配置中配置拦截器
+ * @email huawei_code@163.com
+ * @date 2021/1/21
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TokenVerify {
+
+ // 是否生成校验token
+ boolean generate() default false;
+
+ // 是否校验token
+ boolean verify() default false;
+
+}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/token/TokenVerifyHandler.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/token/TokenVerifyHandler.java
new file mode 100644
index 000000000..f9d5c5df5
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/config/token/TokenVerifyHandler.java
@@ -0,0 +1,45 @@
+package com.youlai.mall.oms.config.token;
+
+import com.youlai.mall.oms.service.TokenService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author huawei
+ * @desc
+ * @email huawei_code@163.com
+ * @date 2021/1/21
+ */
+@Component
+@AllArgsConstructor
+@Slf4j
+public class TokenVerifyHandler extends HandlerInterceptorAdapter {
+
+ @Autowired
+ private TokenService tokenService;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ if (!(handler instanceof HandlerMethod)) {
+ return true;
+ }
+
+ HandlerMethod method = (HandlerMethod) handler;
+ TokenVerify tokenVerify = method.getMethodAnnotation(TokenVerify.class);
+ if (tokenVerify != null) {
+ log.debug("请求:{} 使用 @TokenVerify 注解,被拦截器拦截", request.getRequestURI());
+ // 校验token,判断是否重复提交
+ tokenService.checkToken(request);
+ }
+ return true;
+ }
+
+
+}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/controller/admin/OrderController.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/controller/admin/OrderController.java
new file mode 100644
index 000000000..d36e2993c
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/controller/admin/OrderController.java
@@ -0,0 +1,130 @@
+package com.youlai.mall.oms.controller.admin;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.youlai.common.enums.BusinessTypeEnum;
+import com.youlai.common.enums.QueryModeEnum;
+import com.youlai.common.redis.component.BusinessNoGenerator;
+import com.youlai.common.result.Result;
+import com.youlai.common.result.ResultCode;
+import com.youlai.mall.oms.bo.OrderBO;
+import com.youlai.mall.oms.service.IOmsOrderService;
+import com.youlai.mall.oms.pojo.OmsOrder;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+@Api(tags = "【系统管理】订单服务")
+@RestController("")
+@RequestMapping("/api.admin/v1/orders")
+@Slf4j
+@AllArgsConstructor
+public class OrderController {
+
+ private IOmsOrderService iOmsOrderService;
+
+ @ApiOperation(value = "列表分页", httpMethod = "GET")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "queryMode", value = "查询模式", paramType = "query", dataType = "QueryModeEnum"),
+ @ApiImplicitParam(name = "page", value = "页码", paramType = "query", dataType = "Long"),
+ @ApiImplicitParam(name = "limit", value = "每页数量", paramType = "query", dataType = "Long"),
+ @ApiImplicitParam(name = "orderSn", value = "订单编号", paramType = "query", dataType = "String"),
+ @ApiImplicitParam(name = "status", value = "订单状态", paramType = "query", dataType = "Integer"),
+ @ApiImplicitParam(name = "startDate", value = "开始日期", paramType = "query", dataType = "String"),
+ @ApiImplicitParam(name = "endDate", value = "结束日期", paramType = "query", dataType = "String"),
+ })
+ @GetMapping
+ public Result list(
+ String queryMode,
+ Integer page,
+ Integer limit,
+ String orderSn,
+ Integer status,
+ String startDate,
+ String endDate
+ ) {
+ QueryModeEnum queryModeEnum = QueryModeEnum.getValue(queryMode);
+ switch (queryModeEnum) {
+ case PAGE:
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper()
+ .like(StrUtil.isNotBlank(orderSn), OmsOrder::getOrderSn, orderSn)
+ .eq(status != null, OmsOrder::getStatus, status)
+ .apply(StrUtil.isNotBlank(startDate),
+ "date_format (gmt_crate,'%Y-%m-%d') >= date_format('" + startDate + "','%Y-%m-%d')")
+ .apply(StrUtil.isNotBlank(endDate),
+ "date_format (gmt_crate,'%Y-%m-%d') <= date_format('" + endDate + "','%Y-%m-%d')")
+ .orderByDesc(OmsOrder::getGmtModified)
+ .orderByDesc(OmsOrder::getGmtCreate);
+
+ Page result = iOmsOrderService.page(new Page<>(page, limit), queryWrapper);
+
+ return Result.success(result.getRecords(), result.getTotal());
+ default:
+ return Result.failed(ResultCode.QUERY_MODE_IS_NULL);
+ }
+ }
+
+ @ApiOperation(value = "订单详情", httpMethod = "GET")
+ @ApiImplicitParam(name = "id", value = "订单id", required = true, paramType = "path", dataType = "Long")
+ @GetMapping("/{id}")
+ public Result detail(@PathVariable Long id) {
+ OrderBO order = iOmsOrderService.getByOrderId(id);
+ return Result.success(order);
+ }
+
+ @ApiOperation(value = "订单提交", httpMethod = "POST")
+ @ApiImplicitParam(name = "orderBO", value = "实体JSON对象", required = true, paramType = "body", dataType = "OrderBO")
+ @PostMapping
+ public Result add(@RequestBody OrderBO orderBO) {
+ boolean status = iOmsOrderService.save(orderBO);
+ return Result.judge(status);
+ }
+
+ @ApiOperation(value = "修改订单", httpMethod = "PUT")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "id", value = "订单id", required = true, paramType = "path", dataType = "Long"),
+ @ApiImplicitParam(name = "order", value = "实体JSON对象", required = true, paramType = "body", dataType = "OmsOrder")
+ })
+ @PutMapping(value = "/{id}")
+ public Result update(
+ @PathVariable Long id,
+ @RequestBody OmsOrder order) {
+ boolean status = iOmsOrderService.updateById(order);
+ return Result.judge(status);
+ }
+
+
+ @ApiOperation(value = "订单提交", httpMethod = "POST")
+ @PostMapping("/submit")
+ public Result submit(Boolean openTransaction) {
+ boolean status;
+ if (openTransaction) {
+ status = iOmsOrderService.submitWithGlobalTransactional();
+ } else {
+ status = iOmsOrderService.submit();
+ }
+ return Result.judge(status);
+ }
+
+ @ApiOperation(value = "订单详情", httpMethod = "GET")
+ @ApiImplicitParam(name = "id", value = "订单id", required = true, paramType = "path", dataType = "Long")
+ @GetMapping("/{id}/detail")
+ public Result orderDetail(@PathVariable Long id) {
+ OmsOrder order = iOmsOrderService.getById(id);
+ return Result.success(order);
+ }
+
+ private BusinessNoGenerator businessNoGenerator;
+
+ @PostMapping("/order_sn")
+ public Result generateOrderSn() {
+ String orderSn = businessNoGenerator.generate(BusinessTypeEnum.ORDER);
+ log.info("订单编号:{}", orderSn);
+ return Result.success(orderSn);
+ }
+}
diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/controller/app/CartController.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/controller/app/CartController.java
new file mode 100644
index 000000000..3eb55e4c6
--- /dev/null
+++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/controller/app/CartController.java
@@ -0,0 +1,90 @@
+package com.youlai.mall.oms.controller.app;
+
+import com.youlai.common.result.Result;
+import com.youlai.mall.oms.bo.CartItemBo;
+import com.youlai.mall.oms.bo.CartItemCheckBo;
+import com.youlai.mall.oms.pojo.vo.CartVo;
+import com.youlai.mall.oms.service.CartService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * 购物车
+ *
+ * @author huawei
+ * @email huawei_code@163.com
+ * @date 2020-12-30 22:31:10
+ */
+
+@Api(tags = "购物车接口")
+@RestController
+@RequestMapping("/api.app/v1/carts")
+@Slf4j
+@AllArgsConstructor
+public class CartController {
+
+ private CartService cartService;
+
+ @ApiOperation(value = "查询购物车", httpMethod = "GET")
+ @GetMapping
+ public Result detail() {
+ CartVo cart = cartService.detail();
+ return Result.success(cart);
+ }
+
+ @ApiOperation(value = "添加购物车", httpMethod = "POST")
+ @ApiImplicitParam(name = "skuId", value = "商品SKU Id", required = true, paramType = "param", dataType = "String")
+ @PostMapping
+ public Result