0.学习目标
1.创建订单微服务 加入购物车后,自然就要完成下单,我们接下来创建订单微服务:
1.1.搭建服务 创建model maven工程:
选择位置:
依赖 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 <?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 > leyou</artifactId > <groupId > com.leyou</groupId > <version > 1.0.0-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > ly-order</artifactId > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > tk.mybatis</groupId > <artifactId > mapper-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > com.leyou</groupId > <artifactId > ly-item-interface</artifactId > <version > 1.0.0-SNAPSHOT</version > </dependency > <dependency > <groupId > com.leyou</groupId > <artifactId > ly-common</artifactId > <version > 1.0.0-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper-spring-boot-starter</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
配置文件 application.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 server: port: 8090 spring: application: name: order-service datasource: url: jdbc:mysql://127.0.0.1:3306/heima username: root password: root driver-class-name: com.mysql.jdbc.Driver jackson: default-property-inclusion: non_null eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka mybatis: type-aliases-package: com.leyou.order.entity configuration: map-underscore-to-camel-case: true
启动类 1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan("com.leyou.order.mapper") public class LyOrderApplication { public static void main (String[] args) { SpringApplication.run(LyOrderApplication.class, args); } }
路由 在ly-gateway中添加路由:
1 2 3 4 5 6 zuul: routes: order-service: path: /order/** serviceId: order-service strip-prefix: false
这里选择了strip-prefix
为false,因此路径中的/order
会作为真实请求路径的一部分
1.2.用户登录信息获取 订单业务也需要知道当前登录的用户信息,如同购物车一样,我们需要添加一个SpringMVC的拦截器,用于获取用户信息:
1.2.1.拦截器 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 package com.leyou.order.interceptor;import com.leyou.common.auth.entity.Payload;import com.leyou.common.auth.entity.UserInfo;import com.leyou.common.auth.utils.JwtUtils;import com.leyou.common.threadlocals.UserHolder;import com.leyou.common.utils.CookieUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Slf4j public class UserInterceptor implements HandlerInterceptor { private static final String COOKIE_NAME = "LY_TOKEN" ; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) { try { String token = CookieUtils.getCookieValue(request, COOKIE_NAME); Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, UserInfo.class); UserHolder.setUser(payload.getUserInfo()); return true ; } catch (Exception e) { log.error("【购物车服务】解析用户信息失败!" , e); return false ; } } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserHolder.removeUser(); } }
1.2.2.配置拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.leyou.order.config;import com.leyou.order.interceptor.UserInterceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new UserInterceptor ()).addPathPatterns("/order/**" ); } }
1.3.数据结构 订单表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 CREATE TABLE `tb_order` ( `order_id` bigint (20 ) NOT NULL COMMENT '订单id' , `total_fee` bigint (20 ) NOT NULL COMMENT '总金额,单位为分' , `actual_fee` bigint (20 ) NOT NULL COMMENT '实付金额。单位:分。如:20007,表示:200元7分' , `promotion_ids` varchar (256 ) COLLATE utf8_bin DEFAULT '' COMMENT '优惠活动id,多个以,隔开' , `payment_type` tinyint(1 ) unsigned zerofill NOT NULL COMMENT '支付类型,1、在线支付,2、货到付款' , `post_fee` bigint (20 ) NOT NULL COMMENT '邮费。单位:分。如:20007,表示:200元7分' , `user_id` bigint (20 ) NOT NULL COMMENT '用户id' , `invoice_type` int (1 ) DEFAULT '0' COMMENT '发票类型(0无发票1普通发票,2电子发票,3增值税发票)' , `source_type` int (1 ) DEFAULT '2' COMMENT '订单来源:1:app端,2:pc端,3:微信端' , `status` tinyint(1 ) DEFAULT NULL COMMENT '订单的状态,1、未付款 2、已付款,未发货 3、已发货,未确认 4、确认收货,交易成功 5、交易取消,订单关闭 6、交易结束,已评价' , `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `pay_time` timestamp NULL DEFAULT NULL COMMENT '支付时间' , `consign_time` timestamp NULL DEFAULT NULL COMMENT '发货时间' , `end_time` timestamp NULL DEFAULT NULL COMMENT '交易完成时间' , `close_time` timestamp NULL DEFAULT NULL COMMENT '交易关闭时间' , `comment_time` timestamp NULL DEFAULT NULL COMMENT '评价时间' , `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' , PRIMARY KEY (`order_id`), KEY `buyer_nick` (`user_id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8 COLLATE = utf8_bin;
物流信息表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 CREATE TABLE `tb_order_logistics` ( `order_id` bigint (20 ) NOT NULL COMMENT '订单id,与订单表一对一' , `logistics_number` varchar (18 ) DEFAULT '' COMMENT '物流单号' , `logistics_company` varchar (18 ) DEFAULT '' COMMENT '物流公司名称' , `addressee` varchar (32 ) NOT NULL COMMENT '收件人' , `phone` varchar (11 ) NOT NULL COMMENT '收件人手机号码' , `province` varchar (16 ) NOT NULL COMMENT '省' , `city` varchar (32 ) NOT NULL COMMENT '市' , `district` varchar (32 ) NOT NULL COMMENT '区' , `street` varchar (256 ) NOT NULL COMMENT '街道' , `postcode` int (6 ) DEFAULT '0' COMMENT '邮编' , `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' , PRIMARY KEY (`order_id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
订单条目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CREATE TABLE `tb_order_detail` ( `id` bigint (20 ) NOT NULL AUTO_INCREMENT COMMENT '订单详情id ' , `order_id` bigint (20 ) NOT NULL COMMENT '订单id' , `sku_id` bigint (20 ) NOT NULL COMMENT 'sku商品id' , `num` int (4 ) NOT NULL COMMENT '购买数量' , `title` varchar (256 ) NOT NULL COMMENT '商品标题' , `own_spec` varchar (1024 ) DEFAULT '' COMMENT '商品动态属性键值集' , `price` int (16 ) NOT NULL COMMENT '价格,单位:分' , `image` varchar (256 ) DEFAULT '' COMMENT '商品图片' , `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' , PRIMARY KEY (`id`), KEY `key_order_id` (`order_id`) USING BTREE ) ENGINE= InnoDB AUTO_INCREMENT= 7 DEFAULT CHARSET= utf8 COMMENT= '订单详情表' ;
1.4.基本代码 实体类 Order:
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 package com.leyou.order.entity;import lombok.Data;import javax.persistence.Id;import javax.persistence.Table;import java.util.Date;@Data @Table(name = "tb_order") public class Order { @Id private Long orderId; private Long totalFee; private Long postFee; private Long actualFee; private Integer paymentType; private String promotionIds; private Long userId; private Integer status; private Date createTime; private Date payTime; private Date consignTime; private Date endTime; private Date closeTime; private Date commentTime; private Date updateTime; private Integer invoiceType; private Integer sourceType; }
此处我们为订单状态定义了枚举,方便订单状态的记录:
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 package com.leyou.order.enums;public enum OrderStatusEnum { INIT(1 , "初始化,未付款" ), PAY_UP(2 , "已付款,未发货" ), DELIVERED(3 , "已发货,未确认" ), CONFIRMED(4 , "已确认,未评价" ), CLOSED(5 , "已关闭" ), RATED(6 , "已评价,交易结束" ) ; private Integer value; private String msg; OrderStatusEnum(Integer value, String msg) { this .value = value; this .msg = msg; } public Integer value () { return this .value; } public String msg () { return msg; } }
OrderDetail:
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 package com.leyou.order.entity;import lombok.Data;import tk.mybatis.mapper.annotation.KeySql;import javax.persistence.Id;import javax.persistence.Table;import java.util.Date;@Data @Table(name = "tb_order_detail") public class OrderDetail { @Id @KeySql(useGeneratedKeys = true) private Long id; private Long orderId; private Long skuId; private Integer num; private String title; private Long price; private String ownSpec; private String image; private Date createTime; private Date updateTime; }
物流:OrderLogistics
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 package com.leyou.order.entity;import lombok.Data;import javax.persistence.Id;import java.util.Date;@Data @Table(name = "tb_order_logistics") public class OrderLogistics { @Id private Long orderId; private String logisticsNumber; private String logisticsCompany; private String addressee; private String phone; private String province; private String city; private String district; private String street; private String postcode; private Date createTime; private Date updateTime; }
mapper OrderMapper:
1 2 3 4 5 6 7 package com.leyou.order.mapper;import com.leyou.common.mapper.BaseMapper;import com.leyou.order.entity.Order;public interface OrderMapper extends BaseMapper <Order> {}
OrderDetailMapper:
1 2 3 4 5 6 7 package com.leyou.order.mapper;import com.leyou.common.mapper.BaseMapper;import com.leyou.order.entity.OrderDetail;public interface OrderDetailMapper extends BaseMapper <OrderDetail>{}
OrderLogisticsMapper:
1 2 3 4 5 6 7 8 9 package com.leyou.order.mapper;import com.leyou.common.mapper.BaseMapper;import com.leyou.order.entity.OrderLogistics;public interface OrderLogisticsMapper extends BaseMapper <OrderLogistics> {}
2.订单结算页 在购物车页面,用户会点击去结算
按钮:
随后就会进入订单结算页,展示用户正在购买的商品,并且需要用户选择收货人地址、付款方式等信息:
这个页面需要完成的功能如下:
2.1.收货人信息(作业)
这里的收货人信息肯定是当前登录用户的收货地址。所以需要根据当前登录用户去查询,目前我们在页面是写的假数据:
大家可以在在后台提供地址的增删改查接口,然后页面加载时根据当前登录用户查询,而后赋值给addresses即可。
2.2.支付方式 支付方式有2种:
与我们订单数据中的paymentType
关联:
所以我们可以在Vue实例中定义一个属性来记录支付方式:
然后在页面渲染时与这个变量关联:
效果:
2.3.商品清单 商品清单是通过localstorage从购物车页面传递过来的,到了本页从localstorage取出并且记录在data中:
随后在页面渲染完成:
2.4.提交订单 当点击提交订单
按钮,会看到控制台发起请求:
参数说明:
addressId:收货人地址信息的id,需要去用户中心查询收货人地址
carts:购物车中的商品数据,可以有多个对象
num:购物车中指定商品的购买数量
skuId:购物车中的某商品的id
paymentType:付款方式:1 在线支付,2 货到付款
对应的JS代码:
可以看到返回的提交订单成功,返回的应该是订单的编号id。
3.创建订单接口 订单信息共有3张表,内容很多,但是前台提交的数据却只很少,也就是说我们需要自己填充很多的数据。
3.1.Controller 请求分析:
具体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @RequestMapping("order") public class OrderController { @Autowired private OrderService orderService; @PostMapping public ResponseEntity<Long> createOrder (@RequestBody @Valid OrderDTO orderDTO) { return ResponseEntity.status(HttpStatus.CREATED) .body(this .orderService.createOrder(orderDTO)); } }
3.2.Service 创建订单逻辑比较复杂,需要组装订单数据,基本步骤如下:
组织Order数据,完成新增,包括:
订单编号
用户id
订单金额相关数据,需要查询商品信息后逐个运算并累加获取
订单状态数据
组织OrderDetail数据,完成新增
需要查询商品信息后,封装为OrderDetail集合,然后新增
组织OrderLogistics数据,完成新增
需要查询到收货人地址
然后根据收货人地址,封装OrderLogistics后,完成新增
下单成功后,商品对应库存应该减掉
我们来看其中的几个关键点:
3.2.1.生成订单编号
订单id的特殊性
订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。
雪花算法
这里的订单id是通过一个工具类生成的:
而工具类所采用的生成id算法,是由Twitter公司开源的snowflake(雪花)算法。
简单原理
雪花算法会生成一个64位的二进制数据,为一个Long型。(转换成字符串后长度最多19) ,其基本结构:
第一位:为未使用
第二部分:41位为毫秒级时间(41位的长度可以使用69年)
第三部分:5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
第四部分:最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。
配置
为了保证不重复,我们在application.yaml中给每个部署的节点都配置机器id:
1 2 3 4 ly: worker: workerId: 1 dataCenterId: 1
加载属性:
1 2 3 4 5 6 7 8 @Data @ConfigurationProperties(prefix = "ly.worker") public class IdWorkerProperties { private long workerId; private long dataCenterId; }
编写配置类:
1 2 3 4 5 6 7 8 9 @Configuration @EnableConfigurationProperties(IdWorkerProperties.class) public class IdWorkerConfig { @Bean public IdWorker idWorker (IdWorkerProperties prop) { return new IdWorker (prop.getWorkerId(), prop.getDataCenterId()); } }
3.2.2.查询sku的接口 创建订单过程中,肯定需要查询sku信息,因此我们需要在商品微服务提供根据skuId查询sku的接口:
在ly-item-interface
的ItemClient
中添加接口:
1 2 3 4 5 6 7 @GetMapping("sku/list") List<SkuDTO> querySkuByIds (@RequestParam("ids") List<Long> ids) ;
对应的业务实现之前已经写过了,可以不用写了。
3.2.3.准备物流假数据 我们前端页面传来的是addressId,我们需要根据id查询物流信息,但是因为还没做物流地址管理。所以我们准备一些假数据。
首先是实体类:
我们在ly-user-pojo中添加物流实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.leyou.user.dto;import lombok.Data;@Data public class AddressDTO { private Long id; private Long userId; private String addressee; private String phone; private String province; private String city; private String district; private String street; private String postcode; private Boolean isDefault; }
然后在ly-user-interface的UserClient中添加新功能:
1 2 3 4 5 6 7 8 @GetMapping("/address") AddressDTO queryAddressById (@RequestParam("userId") Long userId, @RequestParam("id") Long id) ;
然后在ly-user-service中编写controller:
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 package com.leyou.user.web;import com.leyou.user.dto.AddressDTO;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("address") public class AddressController { @GetMapping public ResponseEntity<AddressDTO> queryAddressById (@RequestParam("userId") Long userId, @RequestParam("id") Long id) { AddressDTO address = new AddressDTO (); address.setId(1L ); address.setStreet("航头镇航头路18号传智播客 3号楼" ); address.setCity("上海" ); address.setDistrict("浦东新区" ); address.setAddressee("社会我拓哥" ); address.setPhone("15800000000" ); address.setProvince("上海" ); address.setPostcode("210000" ); address.setIsDefault(true ); return ResponseEntity.ok(address); } }
在ly-order的pom.xml中添加user的依赖:
1 2 3 4 5 <dependency > <groupId > com.leyou.service</groupId > <artifactId > ly-user-interface</artifactId > <version > 1.0.0-SNAPSHOT</version > </dependency >
3.2.3.1.屏蔽用户中心的服务间鉴权代码
那么为什么呢?????
3.2.4.批量新增OrderDetail 业务中需要新增OrderDetail,而且是批量的新增,我们需要编写这样的一个SQL。
首先是Mapper接口中的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.leyou.order.mapper;import com.leyou.common.mapper.BaseMapper;import com.leyou.order.entity.OrderDetail;import org.apache.ibatis.annotations.Param;import java.util.List;public interface OrderDetailMapper extends BaseMapper <OrderDetail>{ int insertDetailList (@Param("details") List<OrderDetail> details) ; }
然后编写OrderDetailMapper文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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.leyou.order.mapper.OrderDetailMapper" > <insert id ="insertDetailList" > INSERT INTO `tb_order_detail` ( `id`,`order_id`, `sku_id`,`num`, `title`, `own_spec`, `price`, `image` ) <foreach collection ="details" open ="VALUES" separator ="," item ="detail" > (NULL, #{detail.orderId}, #{detail.skuId}, #{detail.num}, #{detail.title}, #{detail.ownSpec}, #{detail.price}, #{detail.image}) </foreach > </insert > </mapper >
最后,在yaml文件中配置mybatis,读取这个文件:
1 2 mybatis: mapper-locations: mappers/*.xml
3.2.5.减库存接口 创建订单,肯定需要减库存,我们还要在商品微服务提供减库存接口
在ly-item-interface
的GoodsApi
中添加接口:
1 2 3 4 5 6 @PutMapping("/stock/minus") void minusStock (@RequestBody Map<Long, Integer> cartMap) ;
在ly-item-service
的GoodsController
中编写业务:
1 2 3 4 5 6 7 8 9 @PutMapping("/stock/minus") public ResponseEntity<Void> minusStock (@RequestBody Map<Long, Integer> cartMap) { goodsService.minusStock(cartMap); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); }
在ly-item-service
的GoodsServicer
中编写业务
1 2 3 4 5 6 7 8 9 10 11 @Transactional public void minusStock (Map<Long, Integer> cartMap) { for (Map.Entry<Long, Integer> entry : cartMap.entrySet()) { Long skuId = entry.getKey(); Integer num = entry.getValue(); int count = skuMapper.minusStock(skuId, num); if (count != 1 ){ throw new RuntimeException ("库存不足" ); } } }
此处采用了手写Sql在SkuMapper中:
1 2 3 4 5 public interface SkuMapper extends BaseMapper <Sku>, InsertListMapper<Sku> { @Update("UPDATE tb_sku SET stock = stock - #{num} WHERE id = #{id}") int minusStock (@Param("id") Long id, @Param("num") Integer num) ; }
这里减库存并没有采用先查询库存,判断充足才减库存的方案,那样会有线程安全问题,当然可以通过加锁解决。不过我们此处为了效率,并没有使用。
而是把数据库中的库存stock列设置为:无符号类型,当库存减到0以下时,数据库会报错,从而避免超卖:
3.2.5.创建订单代码 完整代码如下:
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 package com.leyou.order.service;import com.leyou.common.enums.ExceptionEnum;import com.leyou.common.exceptions.LyException;import com.leyou.common.threadlocals.UserHolder;import com.leyou.common.utils.BeanHelper;import com.leyou.common.utils.IdWorker;import com.leyou.item.client.ItemClient;import com.leyou.item.dto.SkuDTO;import com.leyou.order.dto.CartDTO;import com.leyou.order.dto.OrderDTO;import com.leyou.order.entity.Order;import com.leyou.order.entity.OrderDetail;import com.leyou.order.entity.OrderLogistics;import com.leyou.order.enums.OrderStatusEnum;import com.leyou.order.mapper.OrderDetailMapper;import com.leyou.order.mapper.OrderLogisticsMapper;import com.leyou.order.mapper.OrderMapper;import com.leyou.user.client.UserClient;import com.leyou.user.dto.AddressDTO;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.stream.Collectors;@Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderDetailMapper detailMapper; @Autowired private OrderLogisticsMapper logisticsMapper; @Autowired private IdWorker idWorker; @Autowired private UserClient userCLient; @Autowired private ItemClient itemClient; @Transactional public Long createOrder (OrderDTO orderDTO) { Order order = new Order (); long orderId = idWorker.nextId(); order.setOrderId(orderId); Long userId = UserHolder.getUser(); order.setUserId(userId); List<CartDTO> carts = orderDTO.getCarts(); List<Long> idList = carts.stream().map(CartDTO::getSkuId).collect(Collectors.toList()); Map<Long, Integer> numMap = carts.stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum)); List<SkuDTO> skuList = itemClient.querySkuByIds(idList); List<OrderDetail> details = new ArrayList <>(); long total = 0 ; for (SkuDTO sku : skuList) { int num = numMap.get(sku.getId()); total += sku.getPrice() * num; OrderDetail detail = new OrderDetail (); detail.setOrderId(orderId); detail.setImage(StringUtils.substringBefore(sku.getImages(), "," )); detail.setNum(num); detail.setSkuId(sku.getId()); detail.setOwnSpec(sku.getOwnSpec()); detail.setPrice(sku.getPrice()); detail.setTitle(sku.getTitle()); details.add(detail); } order.setTotalFee(total); order.setPaymentType(orderDTO.getPaymentType()); order.setActualFee(total + order.getPostFee()); order.setStatus(OrderStatusEnum.INIT.value()); int count = orderMapper.insertSelective(order); if (count != 1 ){ throw new LyException (ExceptionEnum.INSERT_OPERATION_FAIL); } count = detailMapper.insertDetailList(details); if (count != details.size()){ throw new LyException (ExceptionEnum.INSERT_OPERATION_FAIL); } AddressDTO addr = userCLient.queryAddressById(userId, orderDTO.getAddressId()); OrderLogistics logistics = BeanHelper.copyProperties(addr, OrderLogistics.class); logistics.setOrderId(orderId); count = logisticsMapper.insertSelective(logistics); if (count != 1 ){ throw new LyException (ExceptionEnum.INSERT_OPERATION_FAIL); } itemClient.minusStock(numMap); return orderId; } }
3.2.5.删除购物车中已经下单商品(作业) 这里删除购物车中商品,我们可以采用异步的MQ去完成,通过mq通知购物车系统,下单成功,可以删除商品了。在消息体中把sku的id传递。
3.3.测试 启动项目,在页面再次点击提交订单,发现提交成功,跳转到了支付页面:
查看数据库,发现订单已经生成:
订单
订单详情:
订单状态:
4.查询订单接口 4.1.接口分析 支付页面需要展示订单信息,页面加载时,就会发起请求,查询订单信息:
因此我们应该提供查询订单接口:
请求方式:Get
请求路径:/order/{id}
请求参数:路径占位符的id
返回结果:订单对象的json结构,包含订单状态,订单详情,需要定义对应的VO对象
4.2.VO对象 OrderDetailVO:
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 package com.leyou.order.vo;import lombok.Data;@Data public class OrderDetailVO { private Long id; private Long orderId; private Long skuId; private Integer num; private String title; private Long price; private String ownSpec; private String image; }
OrderLogisticsVO:
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 package com.leyou.order.vo;import lombok.Data;@Data public class OrderLogisticsVO { private Long orderId; private String logisticsNumber; private String logisticsCompany; private String addressee; private String phone; private String province; private String city; private String district; private String street; private String postcode; }
OrderVO:
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 package com.leyou.order.vo;import lombok.Data;import javax.persistence.Id;import java.util.Date;import java.util.List;@Data public class OrderVO { @Id private Long orderId; private Long totalFee; private Long postFee = 0L ; private Long actualFee; private Integer paymentType; private String promotionIds; private Long userId; private Integer status; private Date createTime; private Date payTime; private Date consignTime; private Date endTime; private Date closeTime; private Date commentTime; private Integer invoiceType = 0 ; private Integer sourceType = 1 ; private OrderLogisticsVO logistics; private List<OrderDetailVO> detailList; }
4.3.业务 OrderController:
1 2 3 4 @GetMapping("{id}") public ResponseEntity<OrderVO> queryOrderById (@PathVariable("id") Long orderId) { return ResponseEntity.ok(orderService.queryOrderById(orderId)); }
service:
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 public OrderVO queryOrderById (Long id) { Order order = orderMapper.selectByPrimaryKey(id); if (order == null ) { throw new LyException (ExceptionEnum.ORDER_NOT_FOUND); } Long userId = UserHolder.getUser().getId(); if (!userId.equals(order.getUserId())){ throw new LyException (ExceptionEnum.ORDER_NOT_FOUND); } OrderDetail detail = new OrderDetail (); detail.setOrderId(id); List<OrderDetail> details = detailMapper.select(detail); if (CollectionUtils.isEmpty(details)){ throw new LyException (ExceptionEnum.ORDER_NOT_FOUND); } OrderLogistics logistics = logisticsMapper.selectByPrimaryKey(id); if (logistics == null ) { throw new LyException (ExceptionEnum.ORDER_NOT_FOUND); } OrderVO orderVO = BeanHelper.copyProperties(order, OrderVO.class); orderVO.setDetailList(BeanHelper.copyWithCollection(details, OrderDetailVO.class)); orderVO.setLogistics(BeanHelper.copyProperties(logistics, OrderLogisticsVO.class)); return orderVO; }