Day09-苍穹外卖(作业)

前言:

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

今日所做:

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

目录

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("超出配送范围");
}

最后:

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

### 苍穹外卖集成百度地图API教程 #### 一、准备工作 为了使苍穹外卖项目能够成功集成百度地图API,需先完成如下准备事项: - **注册并获取API Key** 访问百度地图开放平台网站,按照指引注册账号,并创建应用以获得专属的API Key。此Key用于后续调用百度地图服务时的身份验证[^1]。 - **引入依赖库** 在项目的`pom.xml`文件中添加必要的Maven依赖项来支持HTTP请求以及JSON解析等功能,以便更好地处理来自百度地图的服务响应数据。对于Java项目而言,可以考虑加入Apache HttpClient和Jackson等相关工具包作为辅助开发组件。 ```xml <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency> ``` #### 二、配置拦截器 通过自定义类实现接口`HandlerInterceptor`,重写`preHandle()`方法,在其中设置每次访问前都需要携带有效的API key参数传递给目标URL地址。这样做的好处是可以集中管理和维护所有对外部服务(如百度地图)发起请求时所需的安全凭证信息[^2]。 ```java public class BaiduMapInterceptor implements HandlerInterceptor { private static final String API_KEY = "your_api_key_here"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 向请求头中添加api-key ((HttpServletResponseWrapper)response).getHeaderWriter().addHeader("ak", API_KEY); return true; } } ``` #### 三、编写业务逻辑代码 根据实际需求设计相应的Service层函数,比如查询地点坐标转换成地理编码等操作。这里给出一个简单的例子——利用百度地图Web服务API中的地理位置逆向解析功能,将输入的地名字符串转化为经纬度数值对的形式返回给前端页面显示出来。 ```java @Service public class LocationService { private static final Logger logger = LoggerFactory.getLogger(LocationService.class); /** * 将地名转为经纬度 */ public LatLng getLocation(String address){ try{ CloseableHttpClient client = HttpClients.createDefault(); URIBuilder builder=new URIBuilder("https://api.map.baidu.com/geocoding/v3/"); builder.setParameter("address", address) .setParameter("output","json") .setParameter("ak",BaiduMapInterceptor.API_KEY); URI uri=builder.build(); HttpGet httpGet = new HttpGet(uri); CloseableHttpResponse httpResponse = client.execute(httpGet); int statusCode=httpResponse.getStatusLine().getStatusCode(); if(statusCode==HttpStatus.SC_OK){ String resultStr=EntityUtils.toString(httpResponse.getEntity(),"UTF-8"); JSONObject jsonObject=new JSONObject(resultStr); double lng=jsonObject.getJSONObject("result").getJSONObject("location").getDouble("lng"); double lat=jsonObject.getJSONObject("result").getJSONObject("location").getDouble("lat"); return new LatLng(lat,lng); }else{ throw new RuntimeException("Failed to get location data from baidu map api."); } }catch (Exception e){ logger.error(e.getMessage(),e); throw new RuntimeException("Error occurred while getting location.",e); } } } // 定义LatLng实体类存储经纬度信息 class LatLng { Double latitude; Double longitude; public LatLng(Double latitude, Double longitude) { this.latitude = latitude; this.longitude = longitude; } // getter and setter methods... } ``` #### 四、测试与部署 最后一步是对上述编写的程序进行全面的功能性和稳定性测试,确保其能够在不同环境下稳定运行。当确认无误之后再将其打包上传至服务器端正式投入使用。如果采用的是分布式架构,则还需要注意做好负载均衡和服务发现等方面的工作;而对于源码版本管理方面则建议使用Git来进行有效跟踪记录每一次修改变更情况[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值