前言:

今天就问题场景-实现思路-核心代码展示-踩坑点/思考点的角度说下第九天的作业

今日所做:

  • 查询历史订单
  • 取消订单
  • 再来一单
  • 用户下单优化

目录

1. 查询历史订单

问题场景:

技术实现思路:

核心代码:

2.取消订单

问题场景:

技术实现思路:

核心代码实现:

问题:

3. 再来一单

问题场景:

技术实现思路:

核心代码实现:

4.用户下单优化

问题场景:

技术实现思路:

核心代码实现:

问题:


1. 查询历史订单

问题场景:

用户需要查看自己的历史订单,并且如果订单数据较多,还需支持分页查询。另外,我需要做到数据隔离,即用户A不能查用户B的数据

技术实现思路:

三步核心逻辑:

1.分页控制

用PageHelper自动拼接LIMIT语句,orderMapper调用orderQuery完成分页

2.权限控制:

从ThreadLocal获取当前用户ID,强制写入SQL语句,比如这这个例子就是将userId分装给DTO,从而参与分页

3.数据组装

将主订单数据于明细订单数据组装成vo,最后跟分页查询得到的数据条数一起返回给前端

核心代码:

1.分页控制

startPage启动分页,DTO设置分页参数,LocalThread获取用户ID,封装进DTO保证数据隔离,

最后将DTO传给orderQuery完成分页并自动统计订单总数

PageHelper.startPage(page, pageSize);
OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO();
ordersPageQueryDTO.setStatus(status);
ordersPageQueryDTO.setPageSize(pageSize);
ordersPageQueryDTO.setPage(page);
Long userId = BaseContext.getCurrentId();
ordersPageQueryDTO.setUserId(userId);

Page<Orders> pages = orderMapper.orderQuery(ordersPageQueryDTO);

2.数据组装

遍历pages,取出每一条订单(Page<Orders>),并且跟orderDetail订单详情进行组装,最后统一封装成orderVO

返回ordervo和订单总数给前端

List<OrderVO> list = new ArrayList<>();
if(pages.getTotal() > 0 && pages != null){
    for(Orders orders : pages){
        Long orderId = orders.getId();
        List<OrderDetail> orderDetailList = orderDetailMapper.getOrderId(orderId);
        OrderVO orderVO = new OrderVO();
        BeanUtils.copyProperties(orders, orderVO);
        orderVO.setOrderDetailList(orderDetailList);
        list.add(orderVO);
    }
}
return new PageResult(pages.getTotal(), list);

2.取消订单

问题场景:

如何实现取消订单,要求:

  • 在订单状态还是为支付或者未接单时,才允许用户取消订单
  • 在订单状态未接单时取消订单,可以执行退款操作

技术实现思路:

核心逻辑分为三步:

1.处理业务异常

2.调用退款接口(如果需要)

3.更新订单状态

核心代码实现:

1.处理业务异常

主要时处理订单是否存在,订单状态是否是未付款或者是未接单。不然不给取消哦

// 根据id查询订单
Orders orderDB = orderMapper.getById(id);
// 校验订单是否存在
if(orderDB == null){
    throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
}
// 订单状态
if(orderDB.getStatus() > 2){
    throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}

2.调用退款接口

如果是未接单的用户,这边给退款

Orders orders = new Orders();
orders.setId(orderDB.getId());
if(orderDB.getStatus().equals(Orders.TO_BE_CONFIRMED)){
    // 调用微信支付退款接口
    weChatPayUtil.refund(
            orderDB.getNumber(),
            orderDB.getNumber(),
            new BigDecimal(0.01),
            new BigDecimal(0.01)
    );

3.更新订单状态

主要是给支付状态改成退款,把订单状态改成取消,设置取消原因,更新取消时间

   // 支付状态改为退款
    orders.setPayStatus(Orders.REFUND);
}
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("用户取消");
orders.setCancelTime(LocalDateTime.now());

orderMapper.update(orders);

问题:

这里为什么要设置两个Orders对象

我们发现这段代码中,我们定义了一个orderDB和一个orders,那么为什么要定义两个呢,定义一个不行吗?答案是行的,但是我们为了追求一个逻辑清晰性,还是将他们区别开

原先我以为定义一个orders是为了防止未更新的值被null覆盖,后面看了下xml文件发现只有传入的值不为null时才会覆盖修改

所以我认为这边定义两个对象应当是为了一个逻辑清晰性:区分“查询数据”和“更新数据

orderDB:

  • 仅用于查询和校验
  • 不修改他的任何字段

orders:

  • 是一个新的空白对象,设置需要显式更新的字段
  • 其他字段保持null

3. 再来一单

问题场景:

当用户下单后,如何再原有基础上再来一单,要求:

  • 点击再来一单后,可以返回购物车界面,购物车中的商品是当前那一单的商品

技术实现思路:

1.权限控制

ThreadLocal得到用户userId,进行数据隔离

2.得到当前订单详情数据

3.将订单详情数据映射到购物车数据

4.将得到的购物车数据插入数据库

核心代码实现:

1.权限控制

获取当前userID,后续传给shoppingCart

// 查询当前用户id
Long userId = BaseContext.getCurrentId();

2.得到当前订单详情数据

// 根据订单id查询当前订单详情
List<OrderDetail> orderDetailList = orderDetailMapper.getOrderId(id);

3.将订单详情数据映射到购物车数据

这里使用了steam流,x是每一个OrderDetail对象,相当于是for遍历,将每一个OrderDetail数据拷贝到相应的新new的shoppingCart对象中

这里拷贝的时候加了“id”的意思是排除id进行拷贝,毕竟这玩意是自增的主键,拷贝没用

List<ShoppingCart> shoppingCartList = orderDetailList.stream()
        .map(x->{
            ShoppingCart shoppingCart = new ShoppingCart();
            // 将原订单详情里面的菜品信息重新复制到购物车对象中
            BeanUtils.copyProperties(x, shoppingCart,"id");
            shoppingCart.setUserId(userId);
            shoppingCart.setCreateTime(LocalDateTime.now());
            return shoppingCart;
        }).collect(Collectors.toList());

4.将得到的购物车数据插入数据库

// 将购物车对象批量添加到数据库中
shoppingCartMapper.insetBatch(shoppingCartList);

商家的订单管理模块我就跳过了,没有什么好说的,有的功能跟用户端是一样的,其他的功能都比较简单。下面我就对用户下单的优化进行一个说明

4.用户下单优化

问题场景:

对原先的用户下单功能进行优化,当用户和商家的距离超过配送范围(5公里)时,则下单失败

技术难点:

1.如何将地址转换成可计算的经纬度

2.如何计算两点间的实际距离

技术实现思路:

同样是三步核心逻辑:

1.地址解析

将商家的地址和用户地址转换成经纬度

2.距离计算

根据两点的经纬度计算行驶距离

3.距离校验

比较计算距离与阈值

核心代码实现:

1.配置准备

yml文件中配置

sky: 
  shop:
     address: ${sky.shop.address}

baidu:
  map:
    ak: ${baidu.map.ak}

dev-yml配置实际值

sky:  
  shop:
    address: "北京市海淀区土地十街10号"

baidu:
  map:
    ak: "你的百度地图AK

2.获取店铺的经纬度坐标

其中shopAddress是在yml文件中定义的店铺位置,这里就是调用百度接口,将实际的地址转换成具体的经纬度坐标

Map map = new HashMap();
map.put("address", shopAddress);
map.put("output", "json");
map.put("ak", ak);

// 获取店铺的经纬度坐标
String shoppingCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);

JSONObject jsonObject = JSON.parseObject(shoppingCoordinate);
if(!jsonObject.getString("status").equals("0")){
    throw new OrderBusinessException("店铺地址解析失败");
}

// 数据解析
JSONObject location = jsonObject.getJSONObject("result").getJSONObject("location");
String lat = location.getString("lat");
String lng = location.getString("lng");
// 店铺经纬度坐标
String shopLngLat = lat + "," + lng;

3.获取用户的经纬度坐标

跟获取店铺经纬度坐标的代码是一样的,唯一区别是地址address换成了用户自己的地址(方法参数传入),作用也是将用户的实际地址转换成具体的经纬度坐标

map.put("address",address);
// 获取用户收获地址的经纬度坐标
String userCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);

jsonObject = JSON.parseObject(userCoordinate);
if(!jsonObject.getString("status").equals("0")){
    throw new OrderBusinessException("收获地址解析失败");
}

// 数据解析
location = jsonObject.getJSONObject("result").getJSONObject("location");
lat = location.getString("lat");
lng = location.getString("lng");
// 店铺经纬度坐标
String userLngLat = lat + "," + lng;

4.进行距离计算

//路线规划
String json = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);
jsonObject = JSON.parseObject(json);
if(!jsonObject.getString("status").equals("0")){
    throw new OrderBusinessException("配送路线规划失败");
}
// 数据解析
// 使用Fastjson解析
JSONObject result = jsonObject.getJSONObject("result");
JSONArray jsonArray = result.getJSONArray("routes");
Integer distance = jsonArray.getJSONObject(0).getInteger("distance");

5.校验距离,异常处理

if(distance > 5000){
    // 配送超过5000米
    throw new OrderBusinessException("超出配送范围");
}

最后在用户下单功能调用该方法即可

// 获取用户地址
String address = addressBook.getProvinceName() + addressBook.getCityName() + addressBook.getDistrictName();
// 检测是否超出范围
checkOutOfRange(address);

问题:

如果遇到如下问题:

不可转换的类型;无法将 'com.google.jsonJsonElement' 转换为 'com.alibaba.fastjson.JSONObject'

这是你在代码里混用了两种JSON的库

  • com.google.jsonJsonElement(Gson库)
  • om.alibaba.fastjson.JSONObject(Fastjson库)

尝试将Gson的JsonArray强制转换为Fastjson的JSONObject,导致类型不兼容

解决:

统一使用一种库,方法也很简单,给你那一块的代码删掉再重新导入就行

// 使用Fastjson解析
JSONObject result = jsonObject.getJSONObject("result");
JSONArray jsonArray = result.getJSONArray("routes");
Integer distance = jsonArray.getJSONObject(0).getInteger("distance");

if (distance > 5000) {
    throw new OrderBusinessException("超出配送范围");
}

最后:

如果我的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!(๑`・ᴗ・´๑)

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐