在该文章中,讲解了较为复杂(相对前面)的业务逻辑,以及第三方API的调用,并进行总结。
今天完成的用户端历史订单模块与商家端订单管理模块都是提高了自身需求分析的能力与crud的掌握。
在点外卖的过程中,会出现各种各样的问题,如用户不想要了,商家没货了等原因,这个时候,需要取消订单。
业务需求
1.订单状态检查(特定状态订单可以取消) 2.退款处理(已支付订单需要退款) 3.订单状态更新 4.异常处理
业务流程图


实现流程
Controller层:
@PutMapping("/cancel/{id}")
@ApiOperation("取消订单")
public Result deleteById(@PathVariable Long id){
log.info("取消订单{}",id);
orderService.deleteById(id);
return Result.success();
}Service层:
@Transactional
public void userCancelById(Long id) throws OrderBusinessException {
log.info("用户取消订单开始,订单ID:{}", id);
try {
// 1. 数据查询
Orders ordersDB = orderMapper.getById(id);
// 2. 业务校验
//判断订单是否存在
if (ordersDB == null) {
throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
}
// 3. 状态检查
if (ordersDB.getStatus() > 2) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
// 4. 业务处理
Orders orders = new Orders();
orders.setId(ordersDB.getId());
// 5. 退款处理
if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
try {
weChatPayUtil.refund(
ordersDB.getNumber(), //商户订单号
ordersDB.getNumber(), //商户退款单号
ordersDB.getAmount(), //退款金额
ordersDB.getAmount() //原订单金额
);
orders.setPayStatus(Orders.REFUND);
} catch (Exception e) {
log.error("退款失败,订单号:{}", ordersDB.getNumber(), e);
throw new OrderBusinessException("退款失败,请稍后重试");
}
}
// 6. 状态更新
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("用户取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
log.info("用户取消订单完成,订单ID:{},原状态:{}", id, ordersDB.getStatus());
} catch (OrderBusinessException e) {
log.warn("用户取消订单失败,订单ID:{},原因:{}", id, e.getMessage());
throw e;
} catch (Exception e) {
log.error("用户取消订单异常,订单ID:{}", id, e);
throw new OrderBusinessException("系统异常,请稍后重试");
}
}优点:
1.使用
@Transactional,通过事务注解保证了数据一致性 2.使用了相对业务的异常类型,保证了精确处理异常 3.使用了日志记录,便于问题排查
在之后的编写业务逻辑中,我将遵循着
验证 → 检查 → 处理 → 更新的流程,让其更便于维护扩展。
在编写代码之前,应进行分析,如上述取消订单功能中,需要考虑商家已接单与商家未接单两种情况,进行取消后是否退款的功能,以及想好相应出现的业务异常,并将其提前拦截。这对我而言,是一个很大的思维提升。
在电商系统中,用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货。
业务分析
业务需求: 1.用户选择收货地址和购物车商品 2.系统需要验证地址是否在配送范围内(第三方API) 3.创建订单和订单详情 4.清空购物车 5.返回订单信息
业务实体 1.用户(User) 2.地址簿(AddressBook) 3.购物车(ShoppingCart) 4.订单(Orders) 5.订单详情(OrderDetail)
分析业务流程 1.参数校验 2.地址范围检查 3.购物车数据获取 4.订单创建 5.订单详情创建 6.购物车清理 7.结果返回
这里讲解常规业务流程,由于API调用较为复杂,放在后面讲解
业务流程图




实现流程 Controller层:
@PostMapping("/submit")
@ApiOperation("用户下单")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
log.info("用户下单:{}", ordersSubmitDTO);
OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);
return Result.success(orderSubmitVO);
} Service层 Service层功能:
1.订单数据验证 2.购物车数据处理 3.订单与订单详情创建 4.购物车清理
@Transactional
@Override
public OrderSubmitVO submit(OrdersSubmitDTO ordersSubmitDTO) {
// 1. 业务异常处理
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
if (addressBook == null) {
throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
// 2. 地址范围检查
checkOutOfRange(addressBook.getCityName()+addressBook.getDistrictName()+addressBook.getDetail());
// 3. 购物车数据获取
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setUserId(BaseContext.getCurrentId());
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
if (shoppingCartList == null || shoppingCartList.size() == 0) {
throw new AddressBookBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
// 4. 订单创建
Orders order = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO, order);
order.setPhone(addressBook.getPhone());
order.setAddress(addressBook.getDetail());
order.setConsignee(addressBook.getConsignee());
order.setNumber(String.valueOf(System.currentTimeMillis()));
order.setUserId(BaseContext.getCurrentId());
order.setStatus(Orders.PENDING_PAYMENT);
order.setPayStatus(Orders.UN_PAID);
order.setOrderTime(LocalDateTime.now());
orderMapper.insert(order);
// 5. 订单详情批量创建
List<OrderDetail> orderDetails = new ArrayList<>();
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(order.getId());
orderDetails.add(orderDetail);
}
orderDetailMapper.insertBatch(orderDetails);
// 6. 购物车清理
shoppingCartMapper.deleteById(BaseContext.getCurrentId());
// 7. 结果封装
OrderSubmitVO build = OrderSubmitVO.builder()
.id(order.getId())
.orderTime(order.getOrderTime())
.orderNumber(order.getNumber())
.orderAmount(order.getAmount())
.build();
return build;
}Service层优点:
1.使用
@Transactional确保数据一致性 2.自定义业务异常,错误信息明确 3.使用BeanUtils.copyProperties进行对象转换 4.订单详情批量插入提高性能
Mapper层: Mapper层功能:
1.订单数据插入 2.订单详情批量插入 3.购物车数据查询与删除
以批量插入的xml为例
<insert id="insertBatch" parameterType="list">
insert into order_detail
(name, order_id, dish_id, setmeal_id, dish_flavor, number, amount, image)
values
<foreach collection="orderDetails" item="od" separator=",">
(#{od.name},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},
#{od.number},#{od.amount},#{od.image})
</foreach>
</insert>业务流程图



实现流程 百度地图API集成功能:
地理编码API:将地址转换为经纬度坐标 路线规划API:计算两点间的驾车距离 配送范围验证:确保订单在可配送范围内
application.yml
sky:
shop:
address: ${sky.shop.address}
baidu:
ak: ${sky.baidu.ak}application-dev.yml 配置外卖商家店铺地址和百度地图的AK
sky:
shop:
address:
baidu:
ak: 改造OrderServiceImpl,注入上面的配置项:
@Value("${sky.shop.address}")
private String shopAddress;
@Value("${sky.baidu.ak}")
private String ak;随后将校验方法加入OrderServiceImpl,在用户下单功能调用即可
校验方法
private void checkOutOfRange(String address) {
Map map = new HashMap();
map.put("address", shopAddress);
map.put("output", "json");
map.put("ak", ak);
// 1. 获取店铺经纬度
String shopCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);
JSONObject jsonObject = JSON.parseObject(shopCoordinate);
// API访问状态不为0则失败,抛出异常
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("店铺地址解析失败");
}
// 2. 解析店铺坐标
JSONObject location = jsonObject.getJSONObject("result").getJSONObject("location");
String lat = location.getString("lat");
String lng = location.getString("lng");
String shopLngLat = lat + "," + lng;
// 3. 获取用户地址经纬度
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("收货地址解析失败");
}
// 4. 解析用户坐标
location = jsonObject.getJSONObject("result").getJSONObject("location");
lat = location.getString("lat");
lng = location.getString("lng");
String userLngLat = lat + "," + lng;
// 5. 路线规划计算距离
map.put("origin", shopLngLat);
map.put("destination", userLngLat);
map.put("steps_info", "0");
String json = HttpClientUtil.doGet("https://api.map.baidu.com/directionlite/v1/driving", map);
jsonObject = JSON.parseObject(json);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("配送路线规划失败");
}
// 6. 距离判断
JSONObject result = jsonObject.getJSONObject("result");
JSONArray jsonArray = (JSONArray) result.get("routes");
Integer distance = (Integer) ((JSONObject) jsonArray.get(0)).get("distance");
if(distance > 5000){
throw new OrderBusinessException("超出配送范围");
}
}本文为苍穹外卖项目学习笔记,持续更新中…
如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。