perf(商城): 下单

流程优化
master
wayn 4 years ago
parent bf20ce91bd
commit abcee5f7db

@ -14,6 +14,7 @@
<module>waynboot-generator</module>
<module>waynboot-mobile-api</module>
<module>waynboot-message</module>
<module>waynboot-message-core</module>
</modules>
<groupId>com.wayn</groupId>
<artifactId>waynboot</artifactId>

@ -53,8 +53,8 @@ wayn:
version: 1.1.0
email: 166738430@qq.com
uploadDir: E:/wayn/upload
adminUrl: http://localhost:81/message/email
mobileUrl: http://localhost:82/message/email
adminUrl: http://localhost:81
mobileUrl: http://localhost:82
# 快递列表
shop:

@ -0,0 +1,37 @@
<?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>waynboot</artifactId>
<groupId>com.wayn</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>waynboot-message-core</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</project>

@ -1,4 +1,4 @@
package com.wayn.message.config;
package com.wayn.message.core.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
@ -16,7 +16,7 @@ public class DirectRabbitConfig {
// 队列 起名TestDirectQueue
@Bean
public Queue TestDirectQueue() {
return new Queue("TestDirectQueue",true);
return new Queue("TestDirectQueue", true);
}
// Direct交换机 起名TestDirectExchange
@ -27,8 +27,23 @@ public class DirectRabbitConfig {
// 绑定 将队列和交换机绑定, 并设置用于匹配键TestDirectRouting
@Bean
Binding bindingDirect() {
Binding bindingTestDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
@Bean
public Queue OrderDirectQueue() {
return new Queue("OrderDirectQueue", true);
}
@Bean
DirectExchange OrderDirectExchange() {
return new DirectExchange("OrderDirectExchange");
}
@Bean
Binding bindingOrderDirect() {
return BindingBuilder.bind(OrderDirectQueue()).to(OrderDirectExchange()).with("OrderDirectRouting");
}
}

@ -0,0 +1,33 @@
package com.wayn.message.core.messsage;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class OrderDTO implements Serializable {
private static final long serialVersionUID = 3237709318648096242L;
/**
* ID
*/
private Long orderId;
/**
*
*/
private List<Long> cartIdArr;
/**
* id
*/
private Long userId;
/**
* id
*/
private Long addressId;
private String message;
}

@ -13,6 +13,12 @@
<dependencies>
<dependency>
<groupId>com.wayn</groupId>
<artifactId>waynboot-message-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>

@ -0,0 +1,49 @@
package com.wayn.message.reciver;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j
@Component
@RabbitListener(queues = "OrderDirectQueue")
public class OrderDirectReceiver {
@Autowired
private RestTemplate restTemplate;
@RabbitHandler
public void process(Map testMessage) {
System.out.println("OrderDirectReceiver消费者收到消息 : " + testMessage.toString());
String notifyUrl = (String) testMessage.get("notifyUrl");
if (StringUtils.isEmpty(notifyUrl)) {
log.error("notifyUrl不能为空参数" + testMessage.toString());
return;
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap();
multiValueMap.add("order", testMessage.get("order"));
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(multiValueMap, headers);
try {
ResponseEntity<String> response = restTemplate.postForEntity(notifyUrl, request, String.class);
if (response.getStatusCode().value() != 200) {
throw new Exception(testMessage.toString() + " 下单失败");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

@ -19,14 +19,14 @@ import java.util.Map;
@Slf4j
@Component
@RabbitListener(queues = "TestDirectQueue")
public class DirectReceiver {
public class TestDirectReceiver {
@Autowired
private RestTemplate restTemplate;
@RabbitHandler
public void process(Map testMessage) {
System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString());
System.out.println("TestDirectReceiver消费者收到消息 : " + testMessage.toString());
String notifyUrl = (String) testMessage.get("notifyUrl");
if (StringUtils.isEmpty(notifyUrl)) {
log.error("notifyUrl不能为空参数" + testMessage.toString());

@ -72,6 +72,26 @@
</exclusions>
</dependency>
<dependency>
<groupId>com.wayn</groupId>
<artifactId>waynboot-message-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</exclusion>
<exclusion>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>

@ -31,10 +31,10 @@ public class OrderController extends BaseController {
return iOrderService.statusCount();
}
@PostMapping("submit")
public R submit(@RequestBody OrderVO orderVO) {
return iOrderService.submit(orderVO);
iOrderService.asyncSubmit(orderVO);
return R.success();
}
@PostMapping("info")

@ -0,0 +1,24 @@
package com.wayn.mobile.api.controller.message;
import com.alibaba.fastjson.JSON;
import com.wayn.common.util.R;
import com.wayn.message.core.messsage.OrderDTO;
import com.wayn.mobile.api.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("message/order")
public class SubmitOrderController {
@Autowired
private IOrderService iOrderService;
@PostMapping("submit")
public R submit(String order) {
OrderDTO orderDTO = JSON.parseObject(order, OrderDTO.class);
return iOrderService.submit(orderDTO);
}
}

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.wayn.common.core.domain.shop.Order;
import com.wayn.common.core.domain.vo.OrderVO;
import com.wayn.common.util.R;
import com.wayn.message.core.messsage.OrderDTO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -25,7 +26,15 @@ public interface IOrderService extends IService<Order> {
* @param orderVO VO
* @return R
*/
R submit(OrderVO orderVO);
R submit(OrderDTO orderDTO);
/**
*
*
* @param orderVO VO
*/
void asyncSubmit(OrderVO orderVO);
/**
* H5
@ -69,6 +78,7 @@ public interface IOrderService extends IService<Order> {
/**
*
*
* @param orderId ID
* @return r
*/

@ -21,7 +21,9 @@ import com.wayn.common.core.util.OrderUtil;
import com.wayn.common.exception.BusinessException;
import com.wayn.common.task.TaskService;
import com.wayn.common.util.R;
import com.wayn.common.util.bean.MyBeanUtil;
import com.wayn.common.util.ip.IpUtils;
import com.wayn.message.core.messsage.OrderDTO;
import com.wayn.mobile.api.domain.Cart;
import com.wayn.mobile.api.mapper.OrderMapper;
import com.wayn.mobile.api.service.ICartService;
@ -35,6 +37,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -78,6 +81,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
private TaskService taskService;
@Autowired
private IMailService iMailService;
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public R selectListPage(IPage<Order> page, Integer showType) {
@ -143,17 +148,24 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
}
@Override
public void asyncSubmit(OrderVO orderVO) {
Map<String, Object> map = new HashMap<>();
OrderDTO orderDTO = new OrderDTO();
MyBeanUtil.copyProperties(orderVO,orderDTO);
map.put("order", orderDTO);
map.put("notifyUrl", WaynConfig.getMobileUrl() + "/message/order/submit");
// 异步发送邮件
rabbitTemplate.convertAndSend("OrderDirectExchange", "OrderDirectRouting", map);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R submit(OrderVO orderVO) {
// 验证用户ID防止用户不一致
Long userId = orderVO.getUserId();
public R submit(OrderDTO orderDTO) {
Long userId = orderDTO.getUserId();
if (Objects.isNull(userId) || !userId.equals(SecurityUtils.getUserId())) {
return R.error("用户ID不一致");
}
// 获取用户地址,为空取默认地址
Long addressId = orderVO.getAddressId();
Long addressId = orderDTO.getAddressId();
Address checkedAddress;
if (Objects.nonNull(addressId)) {
checkedAddress = iAddressService.getById(addressId);
@ -162,7 +174,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
}
// 获取用户订单商品,为空默认取购物车已选中商品
List<Long> cartIdArr = orderVO.getCartIdArr();
List<Long> cartIdArr = orderDTO.getCartIdArr();
List<Cart> checkedGoodsList;
if (CollectionUtils.isEmpty(cartIdArr)) {
checkedGoodsList = iCartService.list(new QueryWrapper<Cart>().eq("checked", true).eq("user_id", userId));
@ -170,6 +182,19 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
checkedGoodsList = iCartService.listByIds(cartIdArr);
}
// 商品货品数量减少
for (Cart checkGoods : checkedGoodsList) {
Long productId = checkGoods.getProductId();
GoodsProduct product = iGoodsProductService.getById(productId);
int remainNumber = product.getNumber() - checkGoods.getNumber();
if (remainNumber < 0) {
throw new RuntimeException("下单的商品货品数量大于库存量");
}
if (!iGoodsProductService.reduceStock(productId, checkGoods.getNumber())) {
throw new BusinessException("商品货品库存减少失败");
}
}
// 商品费用
BigDecimal checkedGoodsPrice = new BigDecimal("0.00");
for (Cart checkGoods : checkedGoodsList) {
@ -204,7 +229,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
order.setOrderStatus(OrderUtil.STATUS_CREATE);
order.setConsignee(checkedAddress.getName());
order.setMobile(checkedAddress.getTel());
order.setMessage(orderVO.getMessage());
order.setMessage(orderDTO.getMessage());
String detailedAddress = checkedAddress.getProvince() + checkedAddress.getCity() + checkedAddress.getCounty() + " " + checkedAddress.getAddressDetail();
order.setAddress(detailedAddress);
order.setFreightPrice(freightPrice);
@ -215,51 +240,38 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
order.setOrderPrice(orderTotalPrice);
order.setActualPrice(actualPrice);
order.setCreateTime(new Date());
if (save(order)) {
Long orderId = order.getId();
// 添加订单商品表项
for (Cart cartGoods : checkedGoodsList) {
// 订单商品
OrderGoods orderGoods = new OrderGoods();
orderGoods.setOrderId(orderId);
orderGoods.setGoodsId(cartGoods.getGoodsId());
orderGoods.setGoodsSn(cartGoods.getGoodsSn());
orderGoods.setProductId(cartGoods.getProductId());
orderGoods.setGoodsName(cartGoods.getGoodsName());
orderGoods.setPicUrl(cartGoods.getPicUrl());
orderGoods.setPrice(cartGoods.getPrice());
orderGoods.setNumber(cartGoods.getNumber());
orderGoods.setSpecifications(cartGoods.getSpecifications());
orderGoods.setCreateTime(LocalDateTime.now());
iOrderGoodsService.save(orderGoods);
}
// 删除购物车里面的商品信息
if (CollectionUtils.isEmpty(cartIdArr)) {
iCartService.remove(new QueryWrapper<Cart>().eq("user_id", userId));
} else {
iCartService.removeByIds(cartIdArr);
}
// 商品货品数量减少
for (Cart checkGoods : checkedGoodsList) {
Long productId = checkGoods.getProductId();
GoodsProduct product = iGoodsProductService.getById(productId);
int remainNumber = product.getNumber() - checkGoods.getNumber();
if (remainNumber < 0) {
throw new RuntimeException("下单的商品货品数量大于库存量");
}
if (!iGoodsProductService.reduceStock(productId, checkGoods.getNumber())) {
throw new BusinessException("商品货品库存减少失败");
}
// 一秒
long delay = 1000;
redisCache.setCacheZset("order_zset", order.getId(), System.currentTimeMillis() + 60 * delay);
taskService.addTask(new CancelOrderTask(order.getId(), delay * 60));
}
return R.success().add("orderId", order.getId());
} else {
if (!save(order)) {
return R.error("订单创建失败");
}
Long orderId = order.getId();
// 添加订单商品表项
for (Cart cartGoods : checkedGoodsList) {
// 订单商品
OrderGoods orderGoods = new OrderGoods();
orderGoods.setOrderId(orderId);
orderGoods.setGoodsId(cartGoods.getGoodsId());
orderGoods.setGoodsSn(cartGoods.getGoodsSn());
orderGoods.setProductId(cartGoods.getProductId());
orderGoods.setGoodsName(cartGoods.getGoodsName());
orderGoods.setPicUrl(cartGoods.getPicUrl());
orderGoods.setPrice(cartGoods.getPrice());
orderGoods.setNumber(cartGoods.getNumber());
orderGoods.setSpecifications(cartGoods.getSpecifications());
orderGoods.setCreateTime(LocalDateTime.now());
iOrderGoodsService.save(orderGoods);
}
// 删除购物车里面的商品信息
if (CollectionUtils.isEmpty(cartIdArr)) {
iCartService.remove(new QueryWrapper<Cart>().eq("user_id", userId));
} else {
iCartService.removeByIds(cartIdArr);
}
// 下单60s内未支付自动取消订单
long delay = 1000;
redisCache.setCacheZset("order_zset", order.getId(), System.currentTimeMillis() + 60 * delay);
taskService.addTask(new CancelOrderTask(order.getId(), delay * 60));
return R.success().add("orderId", order.getId());
}
@Override
@ -396,7 +408,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
// 订单支付成功以后,会发送短信给用户,以及发送邮件给管理员
String email = iMemberService.getById(order.getUserId()).getEmail();
if (StringUtils.isNotBlank(email)) {
iMailService.sendEmail("新订单通知", order.toString(), email, WaynConfig.getMobileUrl());
iMailService.sendEmail("新订单通知", order.toString(), email, WaynConfig.getMobileUrl() + "/message/email");
}
// 删除redis中订单id
redisCache.deleteZsetObject("order_zset", order.getId());
@ -428,7 +440,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
// 订单支付成功以后,会发送短信给用户,以及发送邮件给管理员
String email = iMemberService.getById(order.getUserId()).getEmail();
if (StringUtils.isNotBlank(email)) {
iMailService.sendEmail("新订单通知", order.toString(), email, WaynConfig.getMobileUrl());
iMailService.sendEmail("新订单通知", order.toString(), email, WaynConfig.getMobileUrl() + "/message/email");
}
// 删除redis中订单id
@ -496,7 +508,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
String email = iMemberService.getById(order.getUserId()).getEmail();
if (StringUtils.isNotEmpty(email)) {
if (StringUtils.isNotBlank(email)) {
iMailService.sendEmail("订单正在退款", order.toString(), email, WaynConfig.getMobileUrl());
iMailService.sendEmail("订单正在退款", order.toString(), email, WaynConfig.getMobileUrl()+ "/message/order");
}
}

@ -6,12 +6,11 @@ import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
// @Configuration
public class DirectRabbitConfig {
//队列 起名TestDirectQueue
// 队列 起名TestDirectQueue
@Bean
public Queue TestDirectQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
@ -20,23 +19,37 @@ public class DirectRabbitConfig {
// return new Queue("TestDirectQueue",true,true,false);
//一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("TestDirectQueue",true);
return new Queue("TestDirectQueue", true);
}
//Direct交换机 起名TestDirectExchange
@Bean
public Queue OrderDirectQueue() {
return new Queue("OrderDirectQueue", true);
}
// Direct交换机 起名TestDirectExchange
@Bean
DirectExchange TestDirectExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new DirectExchange("TestDirectExchange",true,false);
return new DirectExchange("TestDirectExchange", true, false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
DirectExchange OrderDirectExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new DirectExchange("OrderDirectExchange", true, false);
}
// 绑定 将队列和交换机绑定, 并设置用于匹配键TestDirectRouting
@Bean
Binding bindingTestDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
@Bean
Binding bindingOrderDirect() {
return BindingBuilder.bind(OrderDirectQueue()).to(OrderDirectExchange()).with("OrderDirectRouting");
}
@Bean
DirectExchange lonelyDirectExchange() {
@ -44,5 +57,4 @@ public class DirectRabbitConfig {
}
}

@ -65,7 +65,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
.antMatchers("/message/**").anonymous()
.antMatchers("/message").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated().and()
.headers().frameOptions().disable();

@ -50,17 +50,17 @@ wayn:
version: 1.1.0
email: 166738430@qq.com
uploadDir: E:/wayn/upload
adminUrl: http://localhost:81/message/email
mobileUrl: http://localhost:82/message/email
adminUrl: http://localhost:81
mobileUrl: http://localhost:82
# wx支付配置
shop:
# 开发者应该设置成自己的wx相关信息
wx:
app-id: wxa5b486c6b918ecfb
app-id: wxe2d425f83d4fbe82
app-secret: e04004829d4c383b4db7769d88dfbca1
mch-id: 111111
mch-key: xxxxxx
mch-id: 1508367571
mch-key: hYaUiN8G34Za3r46x3AMcymG9yhudBH2
notify-url: http://www.example.com/wx/order/pay-notify
# 商户证书文件路径
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3

Loading…
Cancel
Save