1. 搭建订单工程
完成购物车页面之后,点击购物车页面的“去结算”按钮,跳转到订单结算页。
接下来,先搭建订单系统:
![1570802000914](/assets/1570802000914.png)
![1590836152299](/assets/1590836152299.png)
![1570959587143](/assets/1570959587143.png)
pom.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.atguigu</groupId> <artifactId>gmall</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>com.atguigu</groupId> <artifactId>gmall-order</artifactId> <version>0.0.1-SNAPSHOT</version> <name>gmall-order</name> <description>谷粒商城订单系统</description>
<properties> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-pms-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-sms-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-wms-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-ums-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit-test</artifactId> <scope>test</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
|
1.1. 基础配置
bootstrap.yml:
1 2 3 4 5 6 7
| spring: application: name: order-service cloud: nacos: config: server-addr: 127.0.0.1:8848
|
application.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| server: port: 18091 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 zipkin: base-url: http://localhost:9411 discovery-client-enabled: false sender: type: web sleuth: sampler: probability: 1 redis: host: 172.16.116.100 rabbitmq: host: 172.16.116.100 virtual-host: /fengge username: fengge password: fengge listener: simple: acknowledge-mode: manual prefetch: 1 thymeleaf: cache: false feign: sentinel: enabled: true logging: level: com.atguigu.gmall: debug
|
启动类:
1 2 3 4 5 6 7 8 9 10
| @EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class GmallOrderApplication {
public static void main(String[] args) { SpringApplication.run(GmallOrderApplication.class, args); }
}
|
gmall-gateway网关配置添加订单路由:
![1590908732590](/assets/1590908732590.png)
nginx配置:
![1591104423676](/assets/1591104423676.png)
hosts文件中添加order.gmall.com映射。
1.2. 统一获取登录信息
参照gmall-cart购物车中的统一验证
![1591103950152](/assets/1591103950152.png)
LoginInterceptor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @Component public class LoginInterceptor implements HandlerInterceptor {
private static final ThreadLocal<UserInfo> THREAD_LOCAL = new ThreadLocal<>();
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfo userInfo = new UserInfo(); Long userId = Long.valueOf(request.getHeader("userId")); userInfo.setUserId(userId); THREAD_LOCAL.set(userInfo); return true; }
public static UserInfo getUserInfo() { return THREAD_LOCAL.get(); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
THREAD_LOCAL.remove(); } }
|
WebMvcConfig:
1 2 3 4 5 6 7 8 9 10 11
| @Configuration public class WebMvcConfig implements WebMvcConfigurer {
@Autowired private LoginInterceptor loginInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/order/pay/**"); } }
|
UserInfo:
1 2 3 4 5 6
| @Data public class UserInfo {
private Long userId; private String userKey; }
|
1.3. 线程池配置
创建订单也是一个很复杂的业务功能,关系到很多远程调用,这里也可以通过异步调用优化业务。
![1591104168103](/assets/1591104168103.png)
添加线程池配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class ThreadPoolConfig {
@Bean public ThreadPoolExecutor threadPoolExecutor( @Value("${thread.pool.coreSize}")Integer coreSize, @Value("${thread.pool.maxSize}")Integer maxSize, @Value("${thread.pool.keepalive}")Integer keepalive, @Value("${thread.pool.blockQueueSize}")Integer blockQueueSize ){ return new ThreadPoolExecutor(coreSize, maxSize, keepalive, TimeUnit.SECONDS, new ArrayBlockingQueue<>(blockQueueSize)); } }
|
在application.yml中添加线程池配置:
1 2 3 4 5 6
| thread: pool: coreSize: 100 maxSize: 500 keepalive: 60 blockQueueSize: 1000
|
2. 订单结算页
购物车页面点击去结算按钮,应该发送请求到controller获取结算页需要的数据。
需要获取的数据模型,可以参照jd结算页,如下:
![1570961558320](/assets/1570961558320.png)
![1570965591825](/assets/1570965591825.png)
可以发现订单结算页,包含以下信息:
- 收货人信息:有更多地址,即有多个收货地址,其中有一个默认收货地址
- 支付方式:货到付款、在线支付,不需要后台提供
- 送货清单:配送方式(不做)及商品列表(根据购物车选中的skuId到数据库中查询)
- 发票:不做
- 优惠:查询用户领取的优惠券(不做)及可用积分(京豆)
2.1. 数据模型
![1591199241196](/assets/1591199241196.png)
OrderConfirmVO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data public class OrderConfirmVo {
private List<UserAddressEntity> addresses;
private List<OrderItemVo> items;
private Integer bounds;
private String orderToken; }
|
注意:需要引入gmall-ums-interface依赖,并把UserAddressEntity对象移到gmall-ums-interface工程中去。
OrderItemVO:(参照Cart对象)
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Data public class OrderItemVo {
private Long skuId; private String title; private String defaultImage; private BigDecimal price; private Integer count; private BigDecimal weight; private List<SkuAttrValueEntity> saleAttrs; private List<ItemSaleVo> sales; private Boolean store = false; }
|
2.2. 远程数据接口
结合数据模型,需要的远程接口如下:
- 根据当前用户的id查询收货地址(ums_user_address表)
- 查询用户选中的购物车记录
- 根据skuId查询sku(已有)
- 根据skuId查询销售属性(已有)
- 根据skuId查询营销信息(已有)
- 根据skuId查询库存信息(已有)
- 根据当前用户的id查询用户信息(包含积分)
2.2.1. 根据用户id查询收货地址
在gmall-ums中的UserAddressController中添加根据用户id查询收货地址的数据接口:
1 2 3 4 5
| @GetMapping("user/{userId}") public ResponseVo<List<UserAddressEntity>> queryAddressesByUserId(@RequestParam("userId")Long userId){ List<UserAddressEntity> addressEntities = this.userAddressService.list(new QueryWrapper<UserAddressEntity>().eq("user_id", userId)); return ResponseVo.ok(addressEntities); }
|
在gmall-ums-interface工程中的GmallUmsApi添加feign方法:
1 2 3 4 5 6 7
|
@GetMapping("ums/useraddress/user/{userId}") public ResponseVo<List<UserAddressEntity>> queryAddressesByUserId(@RequestParam("userId")Long userId);
|
2.2.2. 根据用户id查询用户积分
在gmall-ums中的UserController已有根据用户id查询用户信息的方法(用户信息中包含积分信息):
![1586691829955](/assets/1586691829955.png)
在gmall-ums-interface工程中的GmallUmsApi添加feign方法:
1 2 3 4 5 6 7
|
@GetMapping("ums/user/{id}") public ResponseVo<UserEntity> queryUserById(@PathVariable("id") Long id);
|
2.2.3. 获取登录用户勾选的购物车
在gmall-carts工程的CartController方法中添加如下方法:
1 2 3 4 5 6
| @GetMapping("check/{userId}") @ResponseBody public ResponseVo<List<Cart>> queryCheckedCarts(@PathVariable("userId")Long userId){ List<Cart> carts = this.cartService.queryCheckedCarts(userId); return ResponseVo.ok(carts); }
|
在CartService中添加如下方法:
1 2 3 4 5 6 7 8 9 10
| public List<Cart> queryCheckedCarts(Long userId) {
String key = KEY_PREFIX + userId; BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(key); List<Object> cartJsons = hashOps.values(); if (CollectionUtils.isEmpty(cartJsons)) { return null; } return cartJsons.stream().map(cartJson -> JSON.parseObject(cartJson.toString(), Cart.class)).filter(cart -> cart.getCheck()).collect(Collectors.toList()); }
|
创建gmall-cart-interface工程,并添加gmallCartApi接口及Cart实体类:
![1586693981657](/assets/1586693981657.png)
1 2 3 4 5
| public interface GmallCartApi {
@GetMapping("check/{userId}") public ResponseVo<List<Cart>> queryCheckedCarts(@PathVariable("userId")Long userId); }
|
2.3. 编写feign接口
在gmall-order工程中添加feign接口:
![1591199352461](/assets/1591199352461.png)
1 2 3
| @FeignClient("cart-service") public interface GmallCartClient extends GmallCartApi { }
|
1 2 3
| @FeignClient("pms-service") public interface GmallPmsClient extends GmallPmsApi { }
|
1 2 3
| @FeignClient("sms-service") public interface GmallSmsClient extends GmallSmsApi { }
|
1 2 3
| @FeignClient("ums-service") public interface GmallUmsClient extends GmallUmsApi { }
|
1 2 3
| @FeignClient("wms-service") public interface GmallWmsClient extends GmallWmsApi { }
|
2.4. 完成订单结算页数据查询接口
![1591199503412](/assets/1591199503412.png)
OrderController:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController public class OrderController {
@Autowired private OrderService orderService;
@GetMapping("confirm") public ResponseVo<OrderConfirmVo> confirm(){
OrderConfirmVo confirmVo = this.orderService.confirm(); return ResponseVo.ok(confirmVo); } }
|
OrderService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| @Service public class OrderService {
@Autowired private GmallPmsClient pmsClient;
@Autowired private GmallSmsClient smsClient;
@Autowired private GmallUmsClient umsClient;
@Autowired private GmallCartClient cartClient;
@Autowired private GmallWmsClient wmsClient;
@Autowired private StringRedisTemplate redisTemplate;
private static final String KEY_PREFIX = "order:token:";
@Autowired private ThreadPoolExecutor threadPoolExecutor;
public OrderConfirmVo confirm() { OrderConfirmVo confirmVo = new OrderConfirmVo();
UserInfo userInfo = LoginInterceptor.getUserInfo(); Long userId = userInfo.getUserId();
CompletableFuture<List<Cart>> cartCompletableFuture = CompletableFuture.supplyAsync(() -> { ResponseVo<List<Cart>> listResponseVo = this.cartClient.queryCheckedCarts(userId); List<Cart> carts = listResponseVo.getData(); if (CollectionUtils.isEmpty(carts)) { throw new OrderException("没有选中的购物车信息!"); } return carts; }, threadPoolExecutor); CompletableFuture<Void> itemCompletableFuture = cartCompletableFuture.thenAcceptAsync(carts -> { List<OrderItemVo> items = carts.stream().map(cart -> { OrderItemVo orderItemVo = new OrderItemVo(); orderItemVo.setSkuId(cart.getSkuId()); orderItemVo.setCount(cart.getCount().intValue()); CompletableFuture<Void> skuCompletableFuture = CompletableFuture.runAsync(() -> { ResponseVo<SkuEntity> skuEntityResponseVo = this.pmsClient.querySkuById(cart.getSkuId()); SkuEntity skuEntity = skuEntityResponseVo.getData(); orderItemVo.setTitle(skuEntity.getTitle()); orderItemVo.setPrice(skuEntity.getPrice()); orderItemVo.setDefaultImage(skuEntity.getDefaultImage()); orderItemVo.setWeight(new BigDecimal(skuEntity.getWeight())); }, threadPoolExecutor); CompletableFuture<Void> saleAttrCompletableFuture = CompletableFuture.runAsync(() -> { ResponseVo<List<SkuAttrValueEntity>> skuAttrValueResponseVo = this.pmsClient.querySkuAttrValuesBySkuId(cart.getSkuId()); List<SkuAttrValueEntity> skuAttrValueEntities = skuAttrValueResponseVo.getData(); orderItemVo.setSaleAttrs(skuAttrValueEntities); }, threadPoolExecutor);
CompletableFuture<Void> saleCompletableFuture = CompletableFuture.runAsync(() -> { ResponseVo<List<ItemSaleVo>> itemSaleVoResponseVo = this.smsClient.querySalesBySkuId(cart.getSkuId()); List<ItemSaleVo> itemSaleVos = itemSaleVoResponseVo.getData(); orderItemVo.setSales(itemSaleVos); }, threadPoolExecutor);
CompletableFuture<Void> storeCompletableFuture = CompletableFuture.runAsync(() -> { ResponseVo<List<WareSkuEntity>> wareSkuResponseVo = this.wmsClient.queryWareSkusBySkuId(cart.getSkuId()); List<WareSkuEntity> wareSkuEntities = wareSkuResponseVo.getData(); if (!CollectionUtils.isEmpty(wareSkuEntities)) { orderItemVo.setStore(wareSkuEntities.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0)); } }, threadPoolExecutor); CompletableFuture.allOf(skuCompletableFuture, saleAttrCompletableFuture, saleCompletableFuture, storeCompletableFuture).join(); return orderItemVo; }).collect(Collectors.toList()); confirmVo.setItems(items); }, threadPoolExecutor);
CompletableFuture<Void> addressCompletableFuture = CompletableFuture.runAsync(() -> { ResponseVo<List<UserAddressEntity>> addressesResponseVo = this.umsClient.queryAddressesByUserId(userId); List<UserAddressEntity> addresses = addressesResponseVo.getData(); confirmVo.setAddresses(addresses); }, threadPoolExecutor);
CompletableFuture<Void> boundsCompletableFuture = CompletableFuture.runAsync(() -> { ResponseVo<UserEntity> userEntityResponseVo = this.umsClient.queryUserById(userId); UserEntity userEntity = userEntityResponseVo.getData(); if (userEntity != null) { confirmVo.setBounds(userEntity.getIntegration()); } }, threadPoolExecutor);
CompletableFuture<Void> tokenCompletableFuture = CompletableFuture.runAsync(() -> { String timeId = IdWorker.getTimeId(); this.redisTemplate.opsForValue().set(KEY_PREFIX + timeId, timeId); confirmVo.setOrderToken(timeId); }, threadPoolExecutor);
CompletableFuture.allOf(itemCompletableFuture, addressCompletableFuture, boundsCompletableFuture, tokenCompletableFuture).join();
return confirmVo; }
}
|
2.5. 测试订单确认页
访问:http://order.gmall.com/confirm
响应数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| { "code": 0, "msg": null, "data": { "addresses": [ { "id": 1, "userId": 2, "name": "柳岩", "phone": "13812345678", "postCode": "200122", "province": "上海", "city": "上海市", "region": "松江区", "address": "大江商厦6层", "defaultStatus": 1 }, { "id": 2, "userId": 2, "name": "锋哥", "phone": "18612345678", "postCode": "200112", "province": "北京", "city": "北京市", "region": "昌平区", "address": "宏福科技园", "defaultStatus": 0 } ], "orderItems": [ { "skuId": 5, "title": "华为 HUAWEI Mate 30 5G 麒麟990 4000万超感光徕卡影像双超级快充8GB+128GB亮黑色5G全网通游戏手机", "defaultImage": "https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-21/a3a0a224-caad-4af2-8eec-f62c0e49a51f_e9ad9735fc3f0686.jpg", "price": 5000.0000, "count": 3, "skuAttrValue": [ { "id": 13, "skuId": 5, "attrId": 3, "attrName": "机身颜色", "attrValue": "黑色", "sort": 0 }, { "id": 14, "skuId": 5, "attrId": 4, "attrName": "运行内存", "attrValue": "8G", "sort": 0 }, { "id": 15, "skuId": 5, "attrId": 5, "attrName": "机身存储", "attrValue": "128G", "sort": 0 } ], "itemSaleVo": [ { "type": "积分", "desc": "成长积分赠送1000.0000,购物积分赠送1000.0000" }, { "type": "满减", "desc": "满5000.0000减100.0000" }, { "type": "打折", "desc": "满2件打0.90折" } ], "weight": 500, "store": null }, { "skuId": 6, "title": "华为 HUAWEI Mate 30 5G 麒麟990 4000万超感光徕卡影像双超级快充8GB+256GB亮黑色5G全网通游戏手机", "defaultImage": "https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-21/f053e068-e43d-46e7-8d67-4964abb240eb_802254cca298ae79 (1).jpg", "price": 6000.0000, "count": 2, "skuAttrValue": [ { "id": 16, "skuId": 6, "attrId": 3, "attrName": "机身颜色", "attrValue": "黑色", "sort": 0 }, { "id": 17, "skuId": 6, "attrId": 4, "attrName": "运行内存", "attrValue": "8G", "sort": 0 }, { "id": 18, "skuId": 6, "attrId": 5, "attrName": "机身存储", "attrValue": "256G", "sort": 0 } ], "itemSaleVo": [ { "type": "积分", "desc": "成长积分赠送2000.0000,购物积分赠送2000.0000" }, { "type": "满减", "desc": "满6000.0000减1500.0000" }, { "type": "打折", "desc": "满2件打8.00折" } ], "weight": 510, "store": null } ], "bounds": 2000, "orderToken": "202004122044380421249317500785655809" } }
|
3. 订单确认页面联调
改造controller方法跳转到trade.html页面:
1 2 3 4 5 6 7
| @GetMapping("confirm") public String confirm(Model model){
OrderConfirmVo confirmVo = this.orderService.confirm(); model.addAttribute("confirmVo", confirmVo); return "trade"; }
|
trade.html总体结构:
![1591256657805](/assets/1591256657805.png)
包含
- 主内容:收件人信息、支付和送货清单、发票信息(略)、积分优惠
- 汇总信息:商品总数、总金额、返现、运费
- 送货信息:应付金额、送货地址等
- 提交订单按钮等
3.1. 主内容
主内容结构如下:
![1591256947967](/assets/1591256947967.png)
3.1.1. 收件人信息
页面模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div class="step-cont"> <div class="addressInfo"> <ul class="addr-detail"> <li class="addr-item"> <div class="choose-address" v-for="address in addresses" :key="address.id"> <div class="con name" :class="address.selected ? 'selected' : ''"><a href="javascript:;" @click="selectAddress(address)"><em>{{address.name}}</em><span title="点击取消选择"></span></a></div> <div class="con address"> <span class="place">{{address.province}}</span> <span class="place">{{address.city}}</span> <span class="place">{{address.region}}</span> <span class="place">{{address.address}}</span> <span class="phone">{{address.phone}}</span> <span class="base" v-if="address.defaultStatus == 1">默认地址</span> </div> <div class="clearfix"></div> </div> </li> </ul> </div> </div>
|
vuejs处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| let app = new Vue({ el: "#app", data: { addresses: [[${confirmVo.addresses}]], order: { orderToken: [[${confirmVo.orderToken}]], address: {}, bounds: [[${confirmVo.bounds}]], items: [[${confirmVo.items}]] } }, created(){ let addresses = this.addresses addresses.forEach(address => { address.selected = false if (address.defaultStatus) { address.selected = true this.order.address = address } }) this.addresses = addresses }, methods: { selectAddress(address){ this.addresses.forEach(address => { address.selected = false }) address.selected = true this.order.address = address } } })
|
3.1.2. 支付方式
![1591257723743](/assets/1591257723743.png)
html模板如下:
1 2 3 4 5 6 7
| <div class="step-cont"> <ul class="payType"> <li :class="order.payType == 1 ? 'selected' : ''" typeValue="1" @click="order.payType=1">在线支付<span title="点击取消选择"></span></li> <li :class="order.payType == 2 ? 'selected' : ''" typeValue="0" @click="order.payType=2">货到付款<span title="点击取消选择"></span></li> <input type="hidden" id="payType" value="1"> </ul> </div>
|
vuejs数据模型如下:
![1591257817329](/assets/1591257817329.png)
3.1.3. 送货清单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <div class="step-cont"> <ul class="send-detail"> <li> <div class="sendType"> <span>配送方式:</span> <ul> <li> <div class="con express">{{order.deliveryCompany}}</div> <div class="con delivery">配送时间:预计8月10日(周三)09:00-15:00送达</div> </li> </ul> </div> <div class="sendGoods"> <span>商品清单:</span> <ul class="yui3-g" v-for="item in order.items" :key="item.skuId"> <li class="yui3-u-1-6"> <span><img :src="item.defaultImage" width="150px" height="200px"/></span> </li> <li class="yui3-u-7-12"> <div class="desc"> <span>{{item.title}}</span><br> <span v-for="saleAttr in item.saleAttrs">{{saleAttr.attrName}}:{{saleAttr.attrValue}} </span> </div> <div class="seven">7天无理由退货</div> </li> <li class="yui3-u-1-12"> <div class="price">¥{{item.price.toFixed(2)}}</div> </li> <li class="yui3-u-1-12"> <div class="num">X{{item.count}}</div> </li> <li class="yui3-u-1-12"> <div class="exit" v-text="item.store ? '有货' : '无货'">有货</div> </li> </ul> </div> <div class="buyMessage"> <span>买家留言:</span> <textarea placeholder="建议留言前先与商家沟通确认" class="remarks-cont"></textarea> </div> </li> </ul> </div>
|
vuejs数据模型处理:
![1591257988402](/assets/1591257988402.png)
3.1.4. 积分优惠
html模板内容:
1 2 3 4 5 6 7 8
| <div class="cardInfo"> <div class="step-tit"> <h5>使用优惠/抵用</h5> </div> <div class="step-cont"> <span>购买积分:{{order.bounds}}</span> </div> </div>
|
对应vue数据模型:
![1591258145873](/assets/1591258145873.png)
3.2. 汇总信息
html模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div class="order-summary"> <div class="static fr"> <div class="list"> <span><i class="number">{{total}}</i>件商品,总商品金额</span> <em class="allprice">¥{{order.totalPrice.toFixed(2)}}</em> </div> <div class="list"> <span>返现:</span> <em class="money">{{returnMoney.toFixed(2)}}</em> </div> <div class="list"> <span>运费:</span> <em class="transport">{{postFee.toFixed(2)}}</em> </div> </div> </div>
|
金额都保留了两位小数。
vuejs数据模型:
![1591258254015](/assets/1591258254015.png)
totalPrice总价格:在created方法中进行了计算
total总件数:在computed计算属性中进行了计算
![1591258410515](/assets/1591258410515.png)
3.3. 送货信息
html模板:
1 2 3 4 5 6 7 8 9
| <div class="clearfix trade"> <div class="fc-price">应付金额: <span class="price">¥{{payAmount.toFixed(2)}}</span></div> <div class="fc-receiverInfo"> 寄送至: <span id="receive-address">{{order.address.city}}{{order.address.region}}{{order.address.address}}</span> 收货人:<span id="receive-name">{{order.address.name}}</span> <span id="receive-phone">{{order.address.phone}}</span> </div> </div>
|
vuejs对应的数据模型:
![1591258650403](/assets/1591258650403.png)
收货地址在收件人信息
已经处理过了,这里可以直接使用。这里还有个应付金额
通过计算属性完成计算:
![1591258748137](/assets/1591258748137.png)
3.4. 提交订单
![1591258852666](/assets/1591258852666.png)
vuejs中提供了提交订单的方法:
1 2 3 4 5 6 7 8 9 10 11
| submitOrder(){ axios.post('http://order.gmall.com/submit', this.order).then(({data}) => { if (data.code === 0) { window.location = 'http://payment.gmall.com/pay.html?orderToken=' + data.data } else { alert(data.data) } }).catch(({response}) => { alert(response.data.message); }) }
|
4. 提交订单
当用户点击提交订单按钮,应该收集页面数据提交到后台并生成订单数据。
请求路径:http://order.gmall.com/submit
需要把该路径添加到网关配置中,进行登录拦截
![1591266058255](/assets/1591266058255.png)
4.1. 数据模型
订单确认页,需要提交的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data public class OrderSubmitVO {
private String orderToken; private BigDecimal totalPrice; private UserAddressEntity address; private Integer payType; private String deliveryCompany; private List<OrderItemVO> items; private Integer bounds; }
|
4.2. 远程接口
4.2.1. 验证库存并锁库存
为了保证验库存和锁库存的原子性,这里直接把验证和锁定库存封装到一个方法中,并在方法中使用分布式锁,防止多人同时锁库存。
gmall-wms工程的pom.xml中引入redisson的依赖:
1 2 3 4 5
| <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.2</version> </dependency>
|
gmall-wms工程的config目录下需要添加redisson的配置文件,参照gmall-index工程:
![1586699712599](/assets/1586699712599.png)
给gmall-wms-interface工程添加实体类:
1 2 3 4 5 6 7 8 9
| @Data public class SkuLockVO {
private Long skuId; private Integer count; private Boolean lock; private Long wareSkuId; private String orderToken; }
|
gmall-wms工程的WareSkuController添加方法:
1 2 3 4 5
| @PostMapping("check/lock") public ResponseVo<List<SkuLockVO>> checkAndLock(@RequestBody List<SkuLockVO> lockVOS){ List<SkuLockVO> skuLockVOS = this.wareSkuService.checkAndLock(lockVOS); return ResponseVo.ok(skuLockVOS); }
|
在添加WareSkuService的接口方法:
1
| List<SkuLockVO> checkAndLock(List<SkuLockVO> lockVOS);
|
在WareSkuServiceImpl实现类中添加方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| @Autowired private WareSkuMapper wareSkuMapper;
@Autowired private StringRedisTemplate redisTemplate;
@Autowired private RedissonClient redissonClient;
private static final String KEY_PREFIX = "store:lock:";
@Transactional @Override public List<SkuLockVO> checkAndLock(List<SkuLockVO> lockVOS) {
if (CollectionUtils.isEmpty(lockVOS)) { return null; }
lockVOS.forEach(lockVO -> { this.checkLock(lockVO); });
List<SkuLockVO> successLockVO = lockVOS.stream().filter(SkuLockVO::getLock).collect(Collectors.toList()); List<SkuLockVO> errorLockVO = lockVOS.stream().filter(skuLockVO -> !skuLockVO.getLock()).collect(Collectors.toList()); if (!CollectionUtils.isEmpty(errorLockVO)) { successLockVO.forEach(lockVO -> { this.wareSkuMapper.unlockStock(lockVO.getWareSkuId(), lockVO.getCount()); }); return lockVOS; }
String orderToken = lockVOS.get(0).getOrderToken(); this.redisTemplate.opsForValue().set(KEY_PREFIX + orderToken, JSON.toJSONString(lockVOS));
return null; }
private void checkLock(SkuLockVO skuLockVO){ RLock fairLock = this.redissonClient.getFairLock("lock:" + skuLockVO.getSkuId()); fairLock.lock(); List<WareSkuEntity> wareSkuEntities = this.wareSkuMapper.checkStock(skuLockVO.getSkuId(), skuLockVO.getCount()); if (CollectionUtils.isEmpty(wareSkuEntities)) { skuLockVO.setLock(false); fairLock.unlock(); return ; } if(this.wareSkuMapper.lockStock(wareSkuEntities.get(0).getId(), skuLockVO.getCount()) == 1){ skuLockVO.setLock(true); skuLockVO.setWareSkuId(wareSkuEntities.get(0).getId()); } else { skuLockVO.setLock(false); } fairLock.unlock(); }
|
在WareSkuMapper中添加方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Mapper public interface WareSkuMapper extends BaseMapper<WareSkuEntity> {
List<WareSkuEntity> checkStock(@Param("skuId") Long skuId, @Param("count") Integer count);
int lockStock(@Param("id") Long id, @Param("count") Integer count);
int unlockStock(@Param("id") Long id, @Param("count") Integer count);
}
|
在WareSkuMapper对应的xml中添加映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.atguigu.gmall.wms.mapper.WareSkuMapper">
<select id="checkStock" resultMap="WareSkuEntity"> select * from wms_ware_sku where stock-stock_locked>=#{count} and sku_id=#{skuId} </select>
<update id="lockStock"> update wms_ware_sku set stock_locked = stock_locked + #{count} where id = #{id} </update>
<update id="unlockStock"> update wms_ware_sku set stock_locked = stock_locked - #{count} where id = #{id} </update>
</mapper>
|
在gmall-wms-interface中的GmallWmsApi中添加接口方法
1 2
| @PostMapping("wms/waresku/check/lock") public ResponseVo<List<SkuLockVO>> checkAndLock(@RequestBody List<SkuLockVO> lockVOS);
|
4.2.2. 创建订单接口
搭建订单接口工程:
![1571288035143](/assets/1571288035143.png)
把OrderSubmitVO和OrderItemVO数据模型移动到gmall-oms-interface中
![1571303781294](/assets/1571303781294.png)
添加api接口:
1 2 3 4 5
| public interface GmallOmsApi {
@PostMapping("oms/order/{userId}") public ResponseVo<OrderEntity> saveOrder(@RequestBody OrderSubmitVO orderSubmitVO, @PathVariable("userId")Long userId); }
|
该api接口对应的实现如下:
给OrderController添加如下方法:
1 2 3 4 5 6 7
| @PostMapping("{userId}") public ResponseVo<OrderEntity> saveOrder(@RequestBody OrderSubmitVO orderSubmitVO, @PathVariable("userId")Long userId){
OrderEntity orderEntity = this.orderService.saveOrder(orderSubmitVO, userId);
return ResponseVo.ok(orderEntity); }
|
给OrderService接口添加如下方法:
1
| OrderEntity saveOrder(OrderSubmitVO orderSubmitVO, Long userId);
|
给OrderServiceImpl实现类添加如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Autowired private OrderItemMapper orderItemMapper;
@Transactional @Override public OrderEntity saveOrder(OrderSubmitVO orderSubmitVO, Long userId) {
OrderEntity orderEntity = new OrderEntity(); BeanUtils.copyProperties(orderSubmitVO, orderEntity); orderEntity.setOrderSn(orderSubmitVO.getOrderToken()); orderEntity.setUserId(userId); orderEntity.setCreateTime(new Date()); orderEntity.setTotalAmount(orderSubmitVO.getTotalPrice()); orderEntity.setPayAmount(orderSubmitVO.getTotalPrice()); orderEntity.setPayType(orderSubmitVO.getPayType()); orderEntity.setStatus(0); orderEntity.setDeliveryCompany(orderSubmitVO.getDeliveryCompany());
this.save(orderEntity);
List<OrderItemVO> orderItems = orderSubmitVO.getItems(); for (OrderItemVO orderItem : orderItems) { OrderItemEntity itemEntity = new OrderItemEntity();
itemEntity.setOrderId(orderEntity.getId()); itemEntity.setOrderSn(orderEntity.getOrderSn());
itemEntity.setSkuId(orderItem.getSkuId()); itemEntity.setSkuName(orderItem.getTitle()); itemEntity.setSkuPrice(orderItem.getPrice()); itemEntity.setSkuQuantity(orderItem.getCount().intValue());
this.orderItemMapper.insert(itemEntity); }
return orderEntity; }
|
4.3. 提交订单基本代码实现
4.3.1. 下单的基本实现
提交订单分以下几个基本步骤:
- 验证令牌防止重复提交
- 验证价格
- 验证库存,并锁定库存
- 生成订单
- 删购物车中对应的记录(消息队列)
OrderController添加方法:
1 2 3 4 5 6 7 8 9 10 11 12
|
@PostMapping("submit") @ResponseBody public ResponseVo<Object> submit(@RequestBody OrderSubmitVo submitVo){
OrderEntity orderEntity = this.orderService.submit(submitVo); return ResponseVo.ok(orderEntity.getOrderSn()); }
|
OrderService中添加方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| @Autowired private GmallOmsClient omsClient;
@Autowired private RabbitTemplate rabbitTemplate;
public OrderEntity submit(OrderSubmitVO submitVO) {
String orderToken = submitVO.getOrderToken(); String script = "if redis.call('get', KEYS[1]) == ARGV[1] " + "then return redis.call('del', KEYS[1]) " + "else return 0 end"; Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(KEY_PREFIX + orderToken), orderToken); if (!flag) { throw new OrderException("您多次提交过快,请稍后再试!"); }
BigDecimal totalPrice = submitVO.getTotalPrice(); List<OrderItemVO> items = submitVO.getItems(); if (CollectionUtils.isEmpty(items)) { throw new OrderException("您没有选中的商品,请选择要购买的商品!"); } BigDecimal currentTotalPrice = items.stream().map(item -> { ResponseVo<SkuEntity> skuEntityResp = this.pmsClient.querySkuById(item.getSkuId()); SkuEntity skuEntity = skuEntityResp.getData(); if (skuEntity != null) { return skuEntity.getPrice().multiply(item.getCount()); } return new BigDecimal(0); }).reduce((t1, t2) -> t1.add(t2)).get(); if (totalPrice.compareTo(currentTotalPrice) != 0) { throw new OrderException("页面已过期,刷新后再试!"); }
List<SkuLockVO> lockVOS = items.stream().map(item -> { SkuLockVO skuLockVO = new SkuLockVO(); skuLockVO.setSkuId(item.getSkuId()); skuLockVO.setCount(item.getCount().intValue()); skuLockVO.setOrderToken(submitVO.getOrderToken()); return skuLockVO; }).collect(Collectors.toList()); ResponseVo<List<SkuLockVO>> skuLockResp = this.wmsClient.checkAndLock(lockVOS); List<SkuLockVO> skuLockVOS = skuLockResp.getData(); if (!CollectionUtils.isEmpty(skuLockVOS)){ throw new OrderException("手慢了,商品库存不足:" + JSON.toJSONString(skuLockVOS)); }
UserInfo userInfo = LoginInterceptor.getUserInfo(); Long userId = userInfo.getUserId(); OrderEntity orderEntity = null; try { ResponseVo<OrderEntity> orderEntityResp = this.omsClient.saveOrder(submitVO, userId); orderEntity = orderEntityResp.getData(); } catch (Exception e) { e.printStackTrace(); this.rabbitTemplate.convertAndSend("ORDER-EXCHANGE", "stock.unlock", orderToken); }
Map<String, Object> map = new HashMap<>(); map.put("userId", userId); List<Long> skuIds = items.stream().map(OrderItemVO::getSkuId).collect(Collectors.toList()); map.put("skuIds", JSON.toJSONString(skuIds)); this.rabbitTemplate.convertAndSend("ORDER-EXCHANGE", "cart.delete", map);
return orderEntity; }
|
4.3.2. 删除购物车监听器
引入rabbitmq依赖及配置rabbitmq的链接信息略。。。。
![1571304547666](/assets/1571304547666.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Component public class CartListener {
@Autowired private StringRedisTemplate redisTemplate;
@Autowired private GmallPmsClient pmsClient;
private static final String KEY_PREFIX = "cart:info:";
@RabbitListener(bindings = @QueueBinding( value = @Queue(value = "order-cart-queue", durable = "true"), exchange = @Exchange(value = "order-exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = {"cart.delete"} )) public void deleteCart(Map<String, Object> map, Channel channel, Message message) throws IOException {
try { Long userId = (Long)map.get("userId"); String skuIdString = map.get("skuIds").toString(); List<String> skuIds = JSON.parseArray(skuIdString, String.class);
BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(KEY_PREFIX + userId); hashOps.delete(skuIds.toArray());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { e.printStackTrace(); if (message.getMessageProperties().getRedelivered()){ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }
|
4.3.3. 库存解锁的监听器
![1571304682491](/assets/1571304682491.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Component public class StockListener {
@Autowired private WareSkuMapper wareSkuMapper;
@Autowired private StringRedisTemplate redisTemplate;
private static final String KEY_PREFIX = "stock:lock:";
@RabbitListener(bindings = @QueueBinding( value = @Queue(value = "order-stock-queue", durable = "true"), exchange = @Exchange(value = "order-exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = {"stock.unlock"} )) public void listener(String orderToken, Channel channel, Message message) throws IOException {
try { String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + orderToken); if (StringUtils.isNotBlank(json)){ List<SkuLockVo> skuLockVos = JSON.parseArray(json, SkuLockVo.class); skuLockVos.forEach(skuLockVo -> { this.wareSkuMapper.unlock(skuLockVo.getWareSkuId(), skuLockVo.getCount()); }); this.redisTemplate.delete(KEY_PREFIX + orderToken); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { e.printStackTrace(); if (message.getMessageProperties().getRedelivered()){ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }
|
4.4. 延时队列定时关单
如果用户下单后一直不支付,库存处于锁定状态,陷入店家商品卖不出,买家无法购买的情况。所以,需要定时关单。
常用解决方案:
- 利用定时任务轮询数据库
缺点:消耗系统内存,增加了数据库的压力,存在较大的时间误差
- rabbitmq的延时队列和死信队列结合(推荐)
声明延时队列的方式,使用如下参数:
1 2 3
| arguments.put("x-dead-letter-exchange", "dlx名称"); arguments.put("x-dead-letter-routing-key", "routingkey"); arguments.put("x-message-ttl", "过期时间");
|
使用延时队列完成定时关单的流程如下:
![1571363284889](/assets/1571363284889.png)
- 订单创建成功,发送消息到创建订单的路由
- 创建订单的路由转发消息给延时队列,延时队列的延时时间就是订单从创建到支付过程,允许的最大等待时间。延时队列不能有消费者(即消息不能被消费)
- 延时时间一到,消息被转入DLX(死信路由)
- 死信路由把死信消息转发给死信队列
- 订单系统监听死信队列,获取到死信消息后,执行关单解库存操作
为了防止在gmall-oms中订单创建成功,而gmall-order中获取响应时网络故障,或删除购物车时失败导致的关单消息发送失败,我们应该在gmall-oms创建订单的方法中发送消息,和订单创建使用一个本地事务,要么都成功要么都失败。
4.4.1. 配置延时队列
在gmall-oms工程中添加rabbitmq的依赖并添加rabbitmq的配置, 略。。。
在gmall-oms工程的config目录下添加rabbitmq的配置类:
![1587045379673](/assets/1587045379673.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Configuration public class RabbitMqConfig {
@Bean public Queue ttlQueue(){ Map<String, Object> arguments = new HashMap<>(); arguments.put("x-message-ttl", 60000); arguments.put("x-dead-letter-exchange", "order-exchange"); arguments.put("x-dead-letter-routing-key", "order.dead"); return new Queue("order-ttl-queue", true, false, false, arguments); }
@Bean public Binding ttlBinding(){
return new Binding("order-ttl-queue", Binding.DestinationType.QUEUE, "order-exchange", "order.create", null); }
@Bean public Queue deadQueue(){ return new Queue("order-dead-queue", true, false, false); }
@Bean public Binding binding(){ return new Binding("order-dead-queue", Binding.DestinationType.QUEUE, "order-exchange", "order.dead", null); } }
|
4.4.2. 实现定时关单
1.发送延时消息
在订单创建成功后,发送消息到延时队列。在gmall-oms工程中的OrderServiceImpl实现类保存订单的最后:
![1587045623910](/assets/1587045623910.png)
2.监听器处理消息
![1571369252203](/assets/1571369252203.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Component public class OrderListener {
@Autowired private OrderMapper orderMapper;
@Autowired private RabbitTemplate rabbitTemplate;
@RabbitListener(queues = {"order-dead-queue"}) public void closeOrder(String orderToken, Channel channel, Message message) throws IOException {
try { if(this.orderMapper.closeOrder(orderToken) == 1){ this.rabbitTemplate.convertAndSend("order-exchange", "stock.unlock", orderToken); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e){ if (message.getMessageProperties().getRedelivered()){ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }
|
给OrderMapper接口添加接口方法:
1 2 3 4 5 6
| @Mapper public interface OrderMapper extends BaseMapper<OrderEntity> {
public int closeOrder(String orderToken);
}
|
给OrderMapper.xml添加映射:
1 2 3 4 5 6 7 8
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.atguigu.gmall.oms.mapper.OrderMapper">
<update id="closeOrder"> update oms_order set `status`=4 where order_sn=#{orderToken} and `status`=0 </update> </mapper>
|
4.4.3. 定时解锁库存
在下单的5个步骤中:如果验库存并锁库存成功,还没来得及执行下单操作,服务器就宕机了,怎么办?
此时,库存已经被锁住,而下单操作还没有执行,这部分锁定的库存无法解锁。所以库存也需要像订单一样定时解锁。
锁定成功要定时解锁库存,在gmall-wms工程WareSkuServiceImpl的checkAndLock验库存并锁库存方法中,库存锁定成功,在返回之前发送一个延时消息。
![1591265154797](/assets/1591265154797.png)
在gmall-wms工程中添加RabbitMqConfig的配置类,配置延时队列及死信队列
![1591265567821](/assets/1591265567821.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Configuration public class RabbitMqConfig {
@Bean public Queue ttlQueue(){ Map<String, Object> arguments = new HashMap<>(); arguments.put("x-message-ttl", 90000); arguments.put("x-dead-letter-exchange", "order-exchange"); arguments.put("x-dead-letter-routing-key", "stock.unlock"); return new Queue("stock-ttl-queue", true, false, false, arguments); }
@Bean public Binding ttlBinding(){
return new Binding("stock-ttl-queue", Binding.DestinationType.QUEUE, "order-exchange", "stock.ttl", null); }
}
|
解锁库存的监听器在4.3.3章节中
已实现