1.续 开发新增订单功能
1.1开发新增订单的业务逻辑层
我们完成订单的新增业务是比较复杂的
可以将整个业务分成三大部分
第一部分是信息的收集
主要是参数类型数据的完整性验证,计算以及转换
第二部分是数据库操作
减少库存,删除购物车,新增订单,和新增订单项
第三部分是收集需要的返回值
我们新增订单成功后,要返回给前端一些信息,例如订单号,实际支付金额等
创建OmsOrderServiceImpl类,代码如下
// 后期秒杀功能会调用这个生成订单的方法
@DubboService
@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService {
@Autowired
private OmsOrderMapper omsOrderMapper;
@Autowired
private OmsOrderItemMapper omsOrderItemMapper;
@DubboReference
private IForOrderSkuService dubboSkuService;
@Autowired
private IOmsCartService omsCartService;
// 当前方法调用了product模块的数据库操作
// 当有运行异常时,所有数据库操作都需要回滚,保证事务的原子性
// 所以要激活分布式事务Seata的功能
@GlobalTransactional
@Override
public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {
// 第一部分:收集信息,准备数据
// OrderAddDTO对象中包含订单和订单项的集合
// 我们要先获得其中订单的信息,做订单的新增
OmsOrder order=new OmsOrder();
// 同名属性赋值过去
BeanUtils.copyProperties(orderAddDTO,order);
// 可以对比OrderAddDTO和OmsOrder类的属性,发现若干属性需要我们来手动赋值
// 以为赋值属性较多,建议单独编写一个方法处理
loadOrder(order);
// 到此为止,order赋值完成
// 下面开始为orderAddDTO对象中的OrderItemAddDTO的集合转换和赋值
// 首先检查它是不是null
List<OrderItemAddDTO> itemAddDTOs=orderAddDTO.getOrderItems();
if(itemAddDTOs==null || itemAddDTOs.isEmpty()){
// 订单中的订单项如果为空,那么抛出异常,终止订单新增业务
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"订单中必须包含至少一件商品");
}
// 我们确认订单中有商品后,需要将现在itemAddDTOs集合中的对象转换为OmsOrderItem类型
// 还需要将不存在的属性赋上值,最后才能进行数据库新增操作
// 所以我们要实例化一个OmsOrderItem类型的集合,接收转换后的对象
List<OmsOrderItem> omsOrderItems=new ArrayList<>();
// 遍历itemAddDTOs
for(OrderItemAddDTO addDTO : itemAddDTOs){
// 还是先将当前遍历的addDTO对象的同名属性赋值给OmsOrderItem
OmsOrderItem orderItem=new OmsOrderItem();
BeanUtils.copyProperties(addDTO,orderItem);
// 赋值id
Long itemId=IdGeneratorUtils.getDistributeId("order_item");
orderItem.setId(itemId);
// 赋值orderid
orderItem.setOrderId(order.getId());
// 将赋好值的orderItem对象添加到集合中
omsOrderItems.add(orderItem);
// 第二部分:执行数据库操作
// 1.减少sku库存
// 获得skuId
Long skuId=orderItem.getSkuId();
// 执行减少库存的方法
int rows=dubboSkuService.reduceStockNum(
skuId,orderItem.getQuantity());
// 判断执行修改影响的行数
if(rows==0){
log.warn("商品skuId:{},库存不足",skuId);
// 库存不足导致运行失败,抛出异常
// Seata会在抛出异常时终止事务
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"库存不足!");
}
// 2.删除购物车信息
OmsCart omsCart=new OmsCart();
omsCart.setUserId(order.getUserId());
omsCart.setSkuId(skuId);
// 执行删除操作
omsCartService.removeUserCarts(omsCart);
}
// 3.新增订单
// OmsOrderMapper调用新增订单的方法即可
omsOrderMapper.insertOrder(order);
// 4.新增订单项
// 使用OmsOrderItemMapper调用新增订单项的方法
omsOrderItemMapper.insertOrderItems(omsOrderItems);
// 第三部分:收集返回值信息最终返回
// 当前方法要求返回OrderAddVO类型
OrderAddVO addVO=new OrderAddVO();
// 给addVO各属性赋值
addVO.setId(order.getId());
addVO.setSn(order.getSn());
addVO.setCreateTime(order.getGmtCreate());
addVO.setPayAmount(order.getAmountOfActualPay());
// 最后别忘了返回!!!!
return addVO;
}
private void loadOrder(OmsOrder order) {
// 针对order对象为空的属性,来赋值,保证新增成功
// 给id赋值,我们使用Leaf分布式方式来赋值
Long id= IdGeneratorUtils.getDistributeId("order");
order.setId(id);
// 赋值用户id
order.setUserId(getUserId());
// 赋值sn(订单号)
order.setSn(UUID.randomUUID().toString());
// 如果订单状态为null赋值为0
if(order.getState()==null)
order.setState(0);
// 为了保证下单时间\创建时间\最后修改时间一致
// 我们单独为他们赋值
LocalDateTime now=LocalDateTime.now();
order.setGmtOrder(now);
order.setGmtCreate(now);
order.setGmtModified(now);
// 后端计算实际支付金额
// 计算公式: 实际支付金额=原价+运费-优惠
// 数据类型BigDecimal,是支持精确计算的类型
BigDecimal price=order.getAmountOfOriginalPrice();
BigDecimal freight=order.getAmountOfFreight();
BigDecimal discount=order.getAmountOfDiscount();
BigDecimal actualPay=price.add(freight).subtract(discount);
// 将计算得到的实际支付金额,赋值到order
order.setAmountOfActualPay(actualPay);
}
@Override
public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
}
@Override
public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
return null;
}
@Override
public OrderDetailVO getOrderDetail(Long id) {
return null;
}
// 业务逻辑层中获得用户信息的方法
// 目标是从SpringSecurity上下文中获取由JWT解析而来的对象
public CsmallAuthenticationInfo getUserInfo(){
// 声明SpringSecurity上下文对象
UsernamePasswordAuthenticationToken authenticationToken=
(UsernamePasswordAuthenticationToken)
SecurityContextHolder.getContext().getAuthentication();
// 为了保险起见,判断一下这个对象是否为空
if(authenticationToken==null){
throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录");
}
// 从上下文中获取登录用户的信息
// 这个信息是由JWT解析获得的
CsmallAuthenticationInfo csmallAuthenticationInfo=
(CsmallAuthenticationInfo) authenticationToken.getCredentials();
// 返回登录信息
return csmallAuthenticationInfo;
}
// 业务逻辑层大多数方法都是只需要用户的ID,所以专门编写一个方法返回id
public Long getUserId(){
return getUserInfo().getId();
}
}
1.2开发新增订单的控制层
下面开始编写控制层
新建OmsOrderController
@RestController
@RequestMapping("/oms/order")
@Api(tags = "订单模块")
public class OmsOrderController {
@Autowired
private IOmsOrderService orderService;
@PostMapping("/add")
@ApiOperation("新增订单的方法")
@PreAuthorize("hasRole('user')")
public JsonResult<OrderAddVO> addOrder(@Validated OrderAddDTO orderAddDTO){
OrderAddVO orderAddVO=orderService.addOrder(orderAddDTO);
return JsonResult.ok(orderAddVO);
}
}
启动Nacos\seata
依次启动服务Leaf\product\passport\order
访问10005执行新增
1.3Seata使用常见错误
Seata在开始工作时,会将方法相关对象序列化后保存在对应数据库的undo_log表中
但是Seata我们序列化的方式支持很多中,常见的jackson格式序列化的情况下,不支持java对象LocalDataTime类型的序列化,序列化运行时会发送错误:
如果见到这样的错误, 就是因为jackson不能序列化LocalDataTime导致的
要想解决,两方面思路,
1.将序列化过程中LocalDataTime类型转换为Date
2.将Seata序列化转换为kryo类型,但是需要在pom文件中添加依赖(我们的项目中有)
<!--解决seata序列化问题-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-serializer-kryo</artifactId>
</dependency>
yml文件使用kryo序列化对象的配置
#seata服务端
seata:
tx-service-group: csmall_group
service:
vgroup-mapping:
csmall_group: default
grouplist:
default: ${my.server.addr}:8091
client:
undo:
log-serialization: kryo
1.4静态资源服务器
1.4.1什么是静态资源服务器
我们无论做什么项目,都会有一些页面中需要显示的静态资源,例如图片,视频文档等
我们一般会创建一个单独的项目,这个项目中保存静态资源
其他项目可以通过我们保存资源的路径访问
使用静态资源服务器的原因是静态资源服务器可以将项目需要的所有图片统一管理起来
当其他模块需要图片时,可以从数据库中直接获得访问静态资源的路径即可
方便管理所有静态资源
1.5前端项目部署
在提供给大家"第五阶段软件和项目.zip"的压缩包中
有csmall-mobile-repo.zip
解压这个压缩包到硬盘
用idea软件open这个解压后文件夹的位置
这个git地址仅在没有压缩包时使用,并不可靠
打开之后要运行这个前端项目
运行前可以参考项目的README.md文件
要运行项目首先需要在Idea的Terminal中输入
npm install
C:\Users\TEDU\IdeaProjects\csmall-mobile-repo>npm install
运行过程中有警告无视掉
运行完毕之后启动项目
npm run serve
C:\Users\TEDU\IdeaProjects\csmall-mobile-repo>npm run serve
之后就可以使用localhost:8080访问了
启动Nacos\Seata\Redis
启动leaf\product\passport\front\order\resource
2.订单查询功能
在新增订单成功之后,用户会看到订单列表
可以按时间查询一段时间范围内的订单列表
我们默认查询当前时间一个月以内的所有订单信息
订单信息要包括oms_order和oms_order_item两个表的信息
所以是一个连表查询
2.1确定关联查询语句
SELECT
oo.id,
oo.sn,
oo.user_id,
oo.contact_name,
oo.state,
oo.amount_of_actual_pay,
oo.gmt_order,
oo.gmt_pay,
oo.gmt_create,
oo.gmt_modified,
ooi.id ooi_id,
ooi.order_id,
ooi.sku_id,
ooi.title,
ooi.price,
ooi.quantity
FROM oms_order oo
JOIN oms_order_item ooi ON oo.id=ooi.order_id
WHERE
oo.user_id=1
AND
oo.gmt_create > '2022-09-01 08:00:00'
AND
oo.gmt_create < NOW()
ORDER BY oo.gmt_modified DESC
2.2开发查询订单的持久层
确定了sql语句之后,要使用在xml文件中
OmsOrderMapper添加方法
// 查询当前用户指定时间范围的所有订单
List<OrderListVO> selectOrdersBetweenTimes(
OrderListTimeDTO orderListTimeDTO);
OmsOrderMapper.xml文件添加对应的内容
<!-- 声明订单和订单项的关联查询映射 -->
<resultMap id="OrderListMap" type="cn.tedu.mall.pojo.order.vo.OrderListVO">
<id column="id" property="id" />
<result column="sn" property="sn" />
<result column="user_id" property="userId" />
<result column="contact_name" property="contactName" />
<result column="state" property="state" />
<result column="amount_of_actual_pay" property="amountOfActualPay" />
<result column="gmt_order" property="gmtOrder" />
<result column="gmt_pay" property="gmtPay" />
<result column="gmt_create" property="gmtCreate" />
<result column="gmt_modified" property="gmtModified" />
<!-- 实体类中有集合,使用collection标签进行映射
property指定java实体类中要映射的集合名称
ofType指定集合的泛型类型
javaType是指定集合类型的,默认就是List
-->
<collection property="orderItems"
ofType="cn.tedu.mall.pojo.order.vo.OrderItemListVO">
<id column="ooi_id" property="id" />
<result column="order_id" property="orderId" />
<result column="sku_id" property="skuId" />
<result column="title" property="title" />
<result column="price" property="price" />
<result column="quantity" property="quantity" />
</collection>
</resultMap>
<!-- 查询当前用户指定时间范围的所有订单 -->
<select id="selectOrdersBetweenTimes" resultMap="OrderListMap" >
SELECT
oo.id,
oo.sn,
oo.user_id,
oo.contact_name,
oo.state,
oo.amount_of_actual_pay,
oo.gmt_order,
oo.gmt_pay,
oo.gmt_create,
oo.gmt_modified,
ooi.id ooi_id,
ooi.order_id,
ooi.sku_id,
ooi.title,
ooi.price,
ooi.quantity
FROM
oms_order oo
JOIN
oms_order_item ooi ON oo.id=ooi.order_id
WHERE
oo.user_id=#{userId}
AND
oo.gmt_create > #{startTime}
AND
oo.gmt_create < #{endTime}
ORDER BY oo.gmt_modified DESC
</select>
2.3开发查询订单业务逻辑层
OmsOrderServiceImpl实现类添加实现方法
// 分页查询当前登录用户在指定时间范围内(默认一个月内)所有订单
// 查询结果OrderListVO包含订单信息和该订单中的订单项(通过xml关联查询实现)
@Override
public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
// 默认查询一个月内的所有订单,所有要判断参数中startTime和endTime是否为空
// 还要判断startTime和endTime时间先后顺序是否合理
validaTimeAndLoadTimes(orderListTimeDTO);
// 获得用户Id
Long userId=getUserId();
// 将用户Id赋值到参数中
orderListTimeDTO.setUserId(userId);
// 设置分页条件
PageHelper.startPage(orderListTimeDTO.getPage(),
orderListTimeDTO.getPageSize());
// 执行关联查询,获得符合条件的订单和订单项
List<OrderListVO> list=omsOrderMapper
.selectOrdersBetweenTimes(orderListTimeDTO);
// 别忘了返回
return JsonPage.restPage(new PageInfo<>(list));
}
private void validaTimeAndLoadTimes(OrderListTimeDTO orderListTimeDTO) {
// 取出起始时间和结束时间
LocalDateTime start=orderListTimeDTO.getStartTime();
LocalDateTime end=orderListTimeDTO.getEndTime();
// 为了不再添加业务的复杂度,当start和end有一个为null 就查进一个月的
if(start==null || end==null){
// 开始时间设置为现在减一个月
start=LocalDateTime.now().minusMonths(1);
end=LocalDateTime.now();
// 赋值到参数中
orderListTimeDTO.setStartTime(start);
orderListTimeDTO.setEndTime(end);
}else{
// 如果start和end都非空
// 要判断start实际是否在end之前
// 如果要编写支持国际时区时间判断,要添加时区的修正
if(end.toInstant(ZoneOffset.of("+8")).toEpochMilli()<
start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
// 如果结束时间小于开始时间,抛出异常,终止业务
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"结束时间应大于起始时间!");
}
}
}
2.4开发查询订单的控制层代码
OmsOrderController
@GetMapping("/list")
@ApiOperation("分页查询当前用户指定时间范围订单")
@PreAuthorize("hasRole('user')")
public JsonResult<JsonPage<OrderListVO>> listUserOrders(
OrderListTimeDTO orderListTimeDTO){
JsonPage<OrderListVO> jsonPage=
orderService.listOrdersBetweenTimes(orderListTimeDTO);
return JsonResult.ok(jsonPage);
}
重启order模块测试即可
作业
删除新增订单业务逻辑层相关内容
重新编写新增订单业务逻辑层代码
梳理逻辑,要求会讲述业务逻辑流程
随笔
在代码中常见的字符表示比较运算符的缩写
lt(less than):
gt(great than);
le(less equals)
ge(great equals)
eq
ne(not equals)