在该文章中,讲解了较为复杂(相对前面)的业务逻辑,以及第三方API的调用,并进行总结。
文章目录
-
- 完成任务清单
- 主要功能展示
-
- 1. 较为复杂的业务逻辑
-
- 取消订单功能
- 知识点收获
- 2. 用户下单功能
-
- 常规业务流程讲解
- API业务流程讲解
完成任务清单
- 用户端历史订单模块
- 商家端订单管理模块
- 优化用户下单模块
主要功能展示
今天完成的用户端历史订单模块与商家端订单管理模块都是提高了自身需求分析的能力与crud的掌握。
1. 较为复杂的业务逻辑
取消订单功能
在点外卖的过程中,会出现各种各样的问题,如用户不想要了,商家没货了等原因,这个时候,需要取消订单。
业务需求
1.订单状态检查(特定状态订单可以取消) 2.退款处理(已支付订单需要退款) 3.订单状态更新 4.异常处理
业务流程图
#mermaid-svg-EEJNHlbWZslUA6xY {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-EEJNHlbWZslUA6xY .error-icon{fill:#552222;}#mermaid-svg-EEJNHlbWZslUA6xY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EEJNHlbWZslUA6xY .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-EEJNHlbWZslUA6xY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EEJNHlbWZslUA6xY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EEJNHlbWZslUA6xY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EEJNHlbWZslUA6xY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EEJNHlbWZslUA6xY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EEJNHlbWZslUA6xY .marker.cross{stroke:#333333;}#mermaid-svg-EEJNHlbWZslUA6xY svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EEJNHlbWZslUA6xY .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-EEJNHlbWZslUA6xY .cluster-label text{fill:#333;}#mermaid-svg-EEJNHlbWZslUA6xY .cluster-label span{color:#333;}#mermaid-svg-EEJNHlbWZslUA6xY .label text,#mermaid-svg-EEJNHlbWZslUA6xY span{fill:#333;color:#333;}#mermaid-svg-EEJNHlbWZslUA6xY .node rect,#mermaid-svg-EEJNHlbWZslUA6xY .node circle,#mermaid-svg-EEJNHlbWZslUA6xY .node ellipse,#mermaid-svg-EEJNHlbWZslUA6xY .node polygon,#mermaid-svg-EEJNHlbWZslUA6xY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EEJNHlbWZslUA6xY .node .label{text-align:center;}#mermaid-svg-EEJNHlbWZslUA6xY .node.clickable{cursor:pointer;}#mermaid-svg-EEJNHlbWZslUA6xY .arrowheadPath{fill:#333333;}#mermaid-svg-EEJNHlbWZslUA6xY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EEJNHlbWZslUA6xY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EEJNHlbWZslUA6xY .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-EEJNHlbWZslUA6xY .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-EEJNHlbWZslUA6xY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EEJNHlbWZslUA6xY .cluster text{fill:#333;}#mermaid-svg-EEJNHlbWZslUA6xY .cluster span{color:#333;}#mermaid-svg-EEJNHlbWZslUA6xY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EEJNHlbWZslUA6xY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是
否
验证订单是否存在
检查订单状态
是否需要退款
处理退款
更新订单状态
记录取消信息
保存到数据库
结束
实现流程
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.使用了日志记录,便于问题排查
知识点收获
在之后的编写业务逻辑中,我将遵循着验证 → 检查 → 处理 → 更新的流程,让其更便于维护扩展。
在编写代码之前,应进行分析,如上述取消订单功能中,需要考虑商家已接单与商家未接单两种情况,进行取消后是否退款的功能,以及想好相应出现的业务异常,并将其提前拦截。这对我而言,是一个很大的思维提升。
2. 用户下单功能
在电商系统中,用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货。
业务分析
业务需求: 1.用户选择收货地址和购物车商品 2.系统需要验证地址是否在配送范围内(第三方API) 3.创建订单和订单详情 4.清空购物车 5.返回订单信息
业务实体 1.用户(User) 2.地址簿(AddressBook) 3.购物车(ShoppingCart) 4.订单(Orders) 5.订单详情(OrderDetail)
分析业务流程 1.参数校验 2.地址范围检查 3.购物车数据获取 4.订单创建 5.订单详情创建 6.购物车清理 7.结果返回
常规业务流程讲解
这里讲解常规业务流程,由于API调用较为复杂,放在后面讲解
业务流程图
#mermaid-svg-d2UYW3BDAupmq41Q {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-d2UYW3BDAupmq41Q .error-icon{fill:#552222;}#mermaid-svg-d2UYW3BDAupmq41Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d2UYW3BDAupmq41Q .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-d2UYW3BDAupmq41Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d2UYW3BDAupmq41Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d2UYW3BDAupmq41Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d2UYW3BDAupmq41Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d2UYW3BDAupmq41Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d2UYW3BDAupmq41Q .marker.cross{stroke:#333333;}#mermaid-svg-d2UYW3BDAupmq41Q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d2UYW3BDAupmq41Q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-d2UYW3BDAupmq41Q .cluster-label text{fill:#333;}#mermaid-svg-d2UYW3BDAupmq41Q .cluster-label span{color:#333;}#mermaid-svg-d2UYW3BDAupmq41Q .label text,#mermaid-svg-d2UYW3BDAupmq41Q span{fill:#333;color:#333;}#mermaid-svg-d2UYW3BDAupmq41Q .node rect,#mermaid-svg-d2UYW3BDAupmq41Q .node circle,#mermaid-svg-d2UYW3BDAupmq41Q .node ellipse,#mermaid-svg-d2UYW3BDAupmq41Q .node polygon,#mermaid-svg-d2UYW3BDAupmq41Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d2UYW3BDAupmq41Q .node .label{text-align:center;}#mermaid-svg-d2UYW3BDAupmq41Q .node.clickable{cursor:pointer;}#mermaid-svg-d2UYW3BDAupmq41Q .arrowheadPath{fill:#333333;}#mermaid-svg-d2UYW3BDAupmq41Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d2UYW3BDAupmq41Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d2UYW3BDAupmq41Q .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-d2UYW3BDAupmq41Q .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-d2UYW3BDAupmq41Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d2UYW3BDAupmq41Q .cluster text{fill:#333;}#mermaid-svg-d2UYW3BDAupmq41Q .cluster span{color:#333;}#mermaid-svg-d2UYW3BDAupmq41Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d2UYW3BDAupmq41Q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
是
否
用户提交订单
接收订单请求
参数校验
地址簿是否存在?
抛出地址簿异常
地址范围检查
获取店铺地址
调用百度地图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业务流程讲解
业务流程图
#mermaid-svg-tlVGGmWeulHd1aN9 {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 .error-icon{fill:#552222;}#mermaid-svg-tlVGGmWeulHd1aN9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tlVGGmWeulHd1aN9 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-tlVGGmWeulHd1aN9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tlVGGmWeulHd1aN9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tlVGGmWeulHd1aN9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tlVGGmWeulHd1aN9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tlVGGmWeulHd1aN9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tlVGGmWeulHd1aN9 .marker.cross{stroke:#333333;}#mermaid-svg-tlVGGmWeulHd1aN9 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tlVGGmWeulHd1aN9 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 .cluster-label text{fill:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 .cluster-label span{color:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 .label text,#mermaid-svg-tlVGGmWeulHd1aN9 span{fill:#333;color:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 .node rect,#mermaid-svg-tlVGGmWeulHd1aN9 .node circle,#mermaid-svg-tlVGGmWeulHd1aN9 .node ellipse,#mermaid-svg-tlVGGmWeulHd1aN9 .node polygon,#mermaid-svg-tlVGGmWeulHd1aN9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tlVGGmWeulHd1aN9 .node .label{text-align:center;}#mermaid-svg-tlVGGmWeulHd1aN9 .node.clickable{cursor:pointer;}#mermaid-svg-tlVGGmWeulHd1aN9 .arrowheadPath{fill:#333333;}#mermaid-svg-tlVGGmWeulHd1aN9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tlVGGmWeulHd1aN9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tlVGGmWeulHd1aN9 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-tlVGGmWeulHd1aN9 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-tlVGGmWeulHd1aN9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tlVGGmWeulHd1aN9 .cluster text{fill:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 .cluster span{color:#333;}#mermaid-svg-tlVGGmWeulHd1aN9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tlVGGmWeulHd1aN9 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
否
是
否
是
是
否
获取店铺地址
调用百度地图API获取店铺经纬度
店铺地址解析成功?
抛出店铺地址解析异常
获取用户收货地址
调用百度地图API获取用户地址经纬度
用户地址解析成功?
抛出用户地址解析异常
路线规划计算距离
调用百度地图路线规划API
路线规划成功?
抛出路线规划异常
距离是否超过5000米?
抛出超出配送范围异常
获取用户购物车数据
实现流程 百度地图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("超出配送范围");
}
}
本文为苍穹外卖项目学习笔记,持续更新中…
如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。
评论前必须登录!
注册