订单功能业务逻辑
用户选中购物车中的商品后,点击添加订单
我们要收集订单信息(sku商品信息,价格信息,优惠和运费信息等)然后才能执行生成订单操作
具体步骤如下
- 首先将用户选中的sku库存减少响应的数量
- 用户购物车要删除对应的商品
- 对应oms_order表执行新增,也就是创建一个订单
- 在新增订单成功后,我们还要将订单中的每种商品和订单关系添加在oms_order_item表中
这个业务中注意的事项或使用的技术有
整体是多个模块的数据操作所以需要seata分布式事务保证数据完整性
修改库存是mallProduct的功能,删除购物车和新增订单时mallOrder模块的功能
开发删除购物车商品功能
因为本次删除是要根据用户的id和skuid进行删除
之前没有开发过,所以要新编写方法
OmsCartMapper添加方法
// 根据用户id和skuId删除商品
void deleteCartByUserIdAndSkuId(OmsCart omsCart);
对应的mapper.xml
<!-- 根据userId和skuId删除购物车中商品 -->
<delete id="deleteCartByUserIdAndSkuId">
delete from
oms_cart
where
user_id=#{userId}
and
sku_id=#{skuId}
</delete>
当前删除购物车商品的功能是为生成订单准备的
所以只需要开发出业务逻辑层即可不需要控制层的代码
// 生成订单时,删除购物车中信息的方法
@Override
public void removeUserCarts(OmsCart omsCart) {
// 根据omsCart的userId和skuId删除购物车信息
omsCartMapper.deleteCartByUserIdAndSkuId(omsCart);
}
开发新增订单功能
开发新增order_Item的持久层
order_item表中保存每张订单包含什么商品的信息
我们新增这个表,要包含订单号,商品id和相关信息
mapper下创建OmsOrderItemMapper
@Repository
public interface OmsOrderItemMapper {
// 新增订单业务时,需要一个能够新增oms_order_item表信息的方法
// 因为一个订单中的商品可能有多个,所以我们新增方法的参数是List
// 在Xml中试下list的遍历,实现连接一次数据库新增多条数据
void insertOrderItems(List<OmsOrderItem> omsOrderItems);
}
OmsOrderItemMapper.xml文件添加内容
<!-- 批量新增OmsOrderItem对象到数据库的方法 -->
<insert id="insertOrderItems" >
insert into oms_order_item(
id,
order_id,
sku_id,
title,
bar_code,
data,
main_picture,
price,
quantity
) values
<foreach collection="omsOrderItems" item="ooi" separator=",">
(
#{ooi.id},
#{ooi.orderId},
#{ooi.skuId},
#{ooi.title},
#{ooi.barCode},
#{ooi.data},
#{ooi.mainPicture},
#{ooi.price},
#{ooi.quantity}
)
</foreach>
</insert>
开发新增Order的持久层
mapper包下再创建OmsOrderMapper
添加新增Order的方法
@Repository
public interface OmsOrderMapper {
// 新增订单对象到数据库的方法
void insertOrder(OmsOrder order);
}
OmsOrderMapper.xml中添加方法
<insert id="insertOrder">
insert into oms_order(
id,
sn,
user_id,
contact_name,
mobile_phone,
telephone,
province_code,
province_name,
city_code,
city_name,
district_code,
district_name,
street_code,
street_name,
detailed_address,
tag,
payment_type,
state,
reward_point,
amount_of_original_price,
amount_of_freight,
amount_of_discount,
amount_of_actual_pay,
gmt_pay,
gmt_order,
gmt_create,
gmt_modified
) VALUES (
#{id},
#{sn},
#{userId},
#{contactName},
#{mobilePhone},
#{telephone},
#{provinceCode},
#{provinceName},
#{cityCode},
#{cityName},
#{districtCode},
#{districtName},
#{streetCode},
#{streetName},
#{detailedAddress},
#{tag},
#{paymentType},
#{state},
#{rewardPoint},
#{amountOfOriginalPrice},
#{amountOfFreight},
#{amountOfDiscount},
#{amountOfActualPay},
#{gmtPay},
#{gmtOrder},
#{gmtCreate},
#{gmtModified}
)
</insert>
开发新增订单的业务逻辑层
所有的业务都需要在业务逻辑层中完成判断和解析以及赋值
OmsOrderServiceImpl类中编写代码如下
@DubboService
@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService {
// 利用Dubbo获得product模块修改库存的功能
@DubboReference
private IForOrderSkuService dubboSkuService;
@Autowired
private IOmsCartService cartService;
@Autowired
private OmsOrderMapper orderMapper;
@Autowired
private OmsOrderItemMapper orderItemMapper;
// 根据提供的订单信息,生成订单
// Seata约束的分布式事务起点
@GlobalTransactional
@Override
public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {
//在连接数据库操作前,一定要先把所有数据准备好
// 1.将订单实体的所有属性赋值OmsOrder
OmsOrder omsOrder=new OmsOrder();
// 将参数orderAddDTO所有同名属性赋值给omsOrder
BeanUtils.copyProperties(orderAddDTO,omsOrder);
// orderAddDTO中数据并不全面,我们需要更详细的信息才能新增订单
// 因为收集信息代码较多,单独编写一个方法实现
loadOrder(omsOrder);
// 2.遍历订单中包含的所有商品的集合,也保证所有属性被赋值OmsOrderItem
// 获得orderAddDTO对象中的所有商品sku集合,判断是否为空
List<OrderItemAddDTO> orderItemAddDTOs=orderAddDTO.getOrderItems();
if(orderItemAddDTOs==null || orderItemAddDTOs.isEmpty()){
// 如果为null或集合中没有元素,抛出异常,终止新增订单
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"订单中必须有商品");
}
// 我们的目标是将订单中的商品增到oms_order_item表中
// 我们持有的持久层方法参数是List<OmsOrderItem>
// 我们先在获得的是List<OrderItemAddDTO>,类型不一致,
// 我们需要讲OrderItemAddDTO转换成OmsOrderItem,保存到一个新的集合里
List<OmsOrderItem> omsOrderItems=new ArrayList<>();
// 遍历参数中包含的所有商品列表
for(OrderItemAddDTO addDTO: orderItemAddDTOs){
// 先是转换我们的OrderItemAddDTO为OmsOrderItem
OmsOrderItem orderItem=new OmsOrderItem();
// 同名属性赋值
BeanUtils.copyProperties(addDTO,orderItem);
// 和Order一样OrderItem也有属性要单独复制
loadOrderItem(orderItem);
// 根据上面方法获得的omsOrder的订单id,给当前订单项的订单id属性赋值
orderItem.setOrderId(omsOrder.getId());
// 到此为止,我们的订单和循环遍历的订单中的订单项都已经赋好值,下面就要开始进行数据库操作了!
// 将收集好信息的orderItem对象添加到omsOrderItems集合中
omsOrderItems.add(orderItem);
// 3.遍历中所有值被赋值后,修改集合中所有商品的库存,并从购物车中删除这些商品
// 减少库存数
// 获得skuId
Long skuId=orderItem.getSkuId();
// 获取减少的商品的数量
Integer quantity=orderItem.getQuantity();
// dubbo调用减少库存的方法
int rows=dubboSkuService.reduceStockNum(skuId,quantity);
// 如果rows值为0,表示这次修改没有修改任何行,一般因为库存不足导致的
if(rows==0){
log.warn("商品skuId:{},库存不足",skuId);
// 抛出异常,Seata组件可以另之前循环过程中已经新增到数据库的信息回滚
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"库存不足");
}
// 删除购物车中商品
// 删除购物车商品的方法需要OmsCart实体类
OmsCart omsCart=new OmsCart();
omsCart.setSkuId(skuId);
omsCart.setUserId(omsOrder.getUserId());
cartService.removeUserCarts(omsCart);
}
// 4.将订单信息新增到数据(包括OrderItem和Order)
// 新增OrderItem对象,利用mapper中批量新增的方法
orderItemMapper.insertOrderItems(omsOrderItems);
// 新增订单表
orderMapper.insertOrder(omsOrder);
// 最后要保证用户能够看到订单详情
// 我们不需要返回订单的所有信息,因为前端包含大部分订单信息
// 我们只需要返回后端生成的一些数据即可
// OrderAddVO完成这个功能
OrderAddVO addVO=new OrderAddVO();
addVO.setId(omsOrder.getId());
addVO.setSn(omsOrder.getSn());
addVO.setCreateTime(omsOrder.getGmtCreate());
addVO.setPayAmount(omsOrder.getAmountOfActualPay());
// 别忘了返回正确的对象
return addVO;
}
private void loadOrderItem(OmsOrderItem orderItem) {
if(orderItem.getId()==null){
Long id=IdGeneratorUtils.getDistributeId("order_item");
orderItem.setId(id);
}
// 必须包含skuid信息,才能确定商品信息
if(orderItem.getSkuId()==null){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"订单中商品必须包含skuId");
}
}
// 新增订单业务中需要的收集Order信息的方法
private void loadOrder(OmsOrder omsOrder) {
// 针对OmsOrder对象为空的值进行收集或生成
// 判断id是否为空
if(omsOrder.getId()==null){
// Leaf获得分布式id
Long id= IdGeneratorUtils.getDistributeId("order");
omsOrder.setId(id);
}
// 判断userId是否为空
if(omsOrder.getUserId()==null){
// 从SpringSecurity容器中获得jwt解析而来的用户id
omsOrder.setUserId(getUserId());
}
// 判断sn
if(omsOrder.getSn()==null){
omsOrder.setSn(UUID.randomUUID().toString());
}
// 判断state
if (omsOrder.getState()==null){
// 如果订单状态为null默认是新生成的订单,状态为0:未支付
omsOrder.setState(0);
}
// 下面要保证订单的生成实际,订单数据的创建实际和最后修改时间一致
// 我们手动获取当前系统时间,统一给他们赋值
if(omsOrder.getGmtOrder()==null){
LocalDateTime now=LocalDateTime.now();
omsOrder.setGmtOrder(now);
omsOrder.setGmtCreate(now);
omsOrder.setGmtModified(now);
}
// 下面是系统计算金额,前端实际上有基本计算显示,但是前安全性相对差,后端还要计算
// 计算基本逻辑 原价+运费-优惠=最终价格
// 判断运费,默认为0
if(omsOrder.getAmountOfFreight()==null){
// 默认运费为0
omsOrder.setAmountOfFreight(new BigDecimal(0.0));
}
// 判断优惠,默认为0
if(omsOrder.getAmountOfDiscount()==null){
omsOrder.setAmountOfDiscount(new BigDecimal(0.0));
}
// 获取传递过来的原价信息,如果原价为空,抛出异常
if(omsOrder.getAmountOfOriginalPrice()==null){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"没有提供订单原价");
}
// 计算实际支付金额
// 原价+运费-优惠=最终价格
BigDecimal originalPrice=omsOrder.getAmountOfOriginalPrice();
BigDecimal freight=omsOrder.getAmountOfFreight();
BigDecimal discount=omsOrder.getAmountOfDiscount();
BigDecimal actualPay=originalPrice.add(freight).subtract(discount);
// 赋值给实际支付属性
omsOrder.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;
}
public CsmallAuthenticationInfo getUserInfo(){
// 获得SpringSecurity容器对象
UsernamePasswordAuthenticationToken authenticationToken=
(UsernamePasswordAuthenticationToken) SecurityContextHolder.
getContext().getAuthentication();
// 判断获取的容器信息是否为空
if(authenticationToken!=null){
// 如果容器中有内容,证明当前容器中有登录用户信息
// 我们获取这个用户信息并返回
CsmallAuthenticationInfo csmallAuthenticationInfo=
(CsmallAuthenticationInfo)authenticationToken.getCredentials();
return csmallAuthenticationInfo;
}
throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录信息");
}
// 业务逻辑层中大多数方法都是获得用户id,所以编写一个返回用户id的方法
public Long getUserId(){
return getUserInfo().getId();
}
}
* 新增oms_order_item的持久层参数是一个集合
* 订单生成时,时间是统一的(gmt_order,gmt_create,gmt_modified)
新建OmsOrderController
@RestController
@RequestMapping("/oms/order")
@Api(tags="订单功能")
public class OmsOrderController {
@Autowired
private IOmsOrderService orderService;
@PostMapping("/add")
@ApiOperation("生成订单的方法")
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult<OrderAddVO> addOrder(@Validated OrderAddDTO orderAddDTO){
OrderAddVO orderAddVO=orderService.addOrder(orderAddDTO);
return JsonResult.ok(orderAddVO);
}
}
启动Nacos\seata
依次启动服务Leaf\product\passport\order
访问10005执行新增
Seata使用常见错误
Seata在开始工作时,会将方法相关对象序列化后保存在对应数据库的undo_log表中
但是Seata我们序列化的方式支持很多中,常见的jackson格式序列化的情况下,不支持java对象LocalDataTime类型的序列化,序列化运行时会发送错误:
如果见到这样的错误, 就是因为jackson不能序列化LocalDataTime导致的
要想解决,两方面思路,
- 将序列化过程中LocalDataTime类型转换为Date
- 将Seata序列化转换为kryo类型,但是需要在pom文件中添加依赖(我们的项目中有)
<!--解决seata序列化问题-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-serializer-kryo</artifactId>
</dependency>
yml文件配置
#seata服务端
seata:
tx-service-group: csmall_group
service:
vgroup-mapping:
csmall_group: default
grouplist:
default: ${my.server.addr}:8091
client:
undo:
log-serialization: kryo
查询订单功能
在新增订单成功之后,用户会看到订单列表
可以按时间查询一段时间范围内的订单列表
我们默认当前时间一个月以内的所有订单信息
订单信息要包括oms_order和oms_order_item两个表的信息
所以是一个连表查询
SELECT
oo.id,
oo.sn,
oo.user_id,
oo.contact_name,
oo.state,
oo.amount_of_actual_pay,
oo.gmt_order,
oo.gmt_create,
oo.gmt_modified,
oo.gmt_pay,
ooi.id ooi_id,
ooi.order_id,
ooi.sku_id,
ooi.title,
ooi.main_picture,
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-2-4 10:00:00'
AND
oo.gmt_create < NOW()
ORDER BY oo.gmt_modified DESC
开发查询订单持久层
确定了sql语句之后,要使用在xml文件中
OmsOrderMapper添加方法
// 查询当前用户指定时间范围内的所有订单
List<OrderListVO> selectOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO);
OmsOrderMapper.xml文件添加对应的内容
因为我们这次查询是一个连接查询,而且返回值是两张表的数据
所以要想正确将数据赋值到java对应的对象中,必须编写正确的关联关系
<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="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="main_picture" property="mainPicture" />
<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_create,
oo.gmt_modified,
oo.gmt_pay,
ooi.id ooi_id,
ooi.order_id,
ooi.sku_id,
ooi.title,
ooi.main_picture,
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>
开发查询订单的业务逻辑层
OmsOrderServiceImpl添加查询订单的方法
// 查询当前登录用户在指定时间范围内(默认一个月内)所有订单
// 订单包含订单信息和订单项信息两个方面(xml的sql语句是关联查询)
@Override
public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
// 因为默认为最近一个月内,如果没有起始和结束时间,需要我们自动添加
// 要检查起始时间和结束时间是否合理,我们单独编写方法校验上面业务
validaTimeAndLoadTimes(orderListTimeDTO);
// 时间验证通过,开始进程查询
// 获得当前用户id
Long userId=getUserId();
// 将userId赋值给参数
orderListTimeDTO.setUserId(userId);
// 设置分页条件
PageHelper.startPage(orderListTimeDTO.getPage(),orderListTimeDTO.getPageSize());
// 执行查询
List<OrderListVO> list=orderMapper.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){
// 起始时间是当前时间减一个月minusMonths就是减月份的意思,1就是一个月
start=LocalDateTime.now().minusMonths(1);
// 默认结束时间是当前时间
end=LocalDateTime.now();
// 赋值给orderListTimeDTO参数
orderListTimeDTO.setStartTime(start);
orderListTimeDTO.setEndTime(end);
}else{
// 如果是国际的时间判断,需要添加时区修正来判断时间
// 判断结束时间大于起始时间,否则发生异常
if(end.toInstant(ZoneOffset.of("+8")).toEpochMilli()<
start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"结束时间应该大于起始时间");
}
}
}
开发查询订单的控制层代码
OmsOrderController
// 查询订单的方法
@GetMapping("/list")
@ApiOperation("分页查询当前登录用户指定时间范围的订单信息")
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult<JsonPage<OrderListVO>> listUserOrders(
OrderListTimeDTO orderListTimeDTO){
JsonPage<OrderListVO> list=orderService.listOrdersBetweenTimes(orderListTimeDTO);
return JsonResult.ok(list);
}
重启order服务测试查询订单功能
更新订单状态
更新订单的状态码
我们电商上面订单的状态修改是非常普通的业务
随着商品的购买流程,订单的状态有
- 0=未支付 (订单默认状态)
- 1=已关闭(超时未支付)
- 2=已取消
- 3=已支付
- 4=已签收
- 5=已拒收
- 6=退款处理中
- 7=已退款
开发更新订单状态的持久层
修改订单状态就是根据订单id修改订单的state
我们随着业务的发展,订单可能需要更多修改的需求
订单的列(字段)比较多,如果每个字段修改,都需要编写一个方法的话,那么方法的数量会非常多
如果我们编写一个方法,能够接收订单对象的实体类参数(OmsOrder)
想修改哪个列,就给哪个对应的数据赋值即可
Mybatis中可以通过编写动态修改sql语句完成这个需求
OmsOrderMapper接口添加方法
// 动态修改订单的sql,根据给定的id值,修改各列的值
void updateOrderById(OmsOrder omsOrder);
下面转到对应的xml文件编写动态sql
<!-- 动态修改sql,能够根据OmsOrder对象的值来修改指定的字段\列 -->
<!-- 在sql语句中,判断OmsOrder的属性是否为空,如果为空,不修改,如果不为空,生成修改语句 -->
<!-- sql语句中使用set标签
1.能生成set关键字
2.能够将动态生成的所有语句中,最后一个","(逗号)删除
-->
<update id="updateOrderById">
update oms_order
<set>
<if test="contactName!=null">
contact_name=#{contactName},
</if>
<if test="mobilePhone!=null">
mobile_phone=#{mobilePhone},
</if>
<if test="telephone!=null">
telephone=#{telephone},
</if>
<if test="streetCode!=null">
street_code=#{streetCode},
</if>
<if test="streetName!=null">
street_name=#{streetName},
</if>
<if test="detailedAddress!=null">
detailed_address=#{detailedAddress},
</if>
<if test="tag!=null">
tag=#{tag},
</if>
<if test="paymentType!=null">
payment_type=#{paymentType},
</if>
<if test="state!=null">
state=#{state},
</if>
<if test="rewardPoint!=null">
reward_point=#{rewardPoint},
</if>
<if test="amountOfOriginalPrice!=null">
amount_of_original_price=#{amountOfOriginalPrice},
</if>
<if test="amountOfFreight!=null">
amount_of_freight=#{amountOfFreight},
</if>
<if test="amountOfDiscount!=null">
amount_of_discount=#{amountOfDiscount},
</if>
<if test="amountOfActualPay!=null">
amount_of_actual_pay=#{amountOfActualPay},
</if>
<if test="gmtPay!=null">
gmt_pay=#{gmtPay},
</if>
</set>
where id=#{id}
</update>
开发修改订单状态的业务逻辑层
OmsOrderServiceImpl
// 根据订单id 修改订单状态
@Override
public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
// 参数orderStateUpdateDTO包含订单id和状态码
// 我们修改订单的方法参数是OmsOrder,所以需要实例化这个类型对象并赋值
OmsOrder omsOrder=new OmsOrder();
BeanUtils.copyProperties(orderStateUpdateDTO,omsOrder);
// 指定修改订单的方法
// 因为现在OmsOrder中只有id和state属性,所以不会修改其他列
orderMapper.updateOrderById(omsOrder);
}
开发控制层
OmsOrderController
// 修改订单状态的方法
@PostMapping("/update/state")
@ApiOperation("修改订单状态的方法")
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult updateOrderState(@Validated OrderStateUpdateDTO orderStateUpdateDTO){
orderService.updateOrderState(orderStateUpdateDTO);
return JsonResult.ok();
}
重启Order
测试功能
测试时根据实际数据库订单id,修改knife4j的数据然后再运行
学习记录,如有侵权请联系删除
本文暂时没有评论,来添加一个吧(●'◡'●)