前言:
今天就问题场景-实现思路-核心代码展示-踩坑点/思考点的角度说下第九天的作业
今日所做:
- 查询历史订单
- 取消订单
- 再来一单
- 用户下单优化
目录
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("超出配送范围");
}
最后:
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!(๑`・ᴗ・´๑)