省钱吃外卖App后端实战:Java基于WebFlux实现非阻塞式的第三方接口调用

省钱吃外卖App后端实战:Java基于WebFlux实现非阻塞式的第三方接口调用

在构建“省钱吃外卖”这类高并发的CPS返利应用时,后端服务的响应速度直接决定了用户体验。这类应用的核心逻辑往往不是复杂的计算,而是频繁地调用第三方外卖平台(如美团、饿了么)的API来获取商品、下单或查询订单状态。

传统的Spring MVC基于Servlet的阻塞式I/O模型,在面对大量外部API调用时,会为每个请求分配一个线程。当第三方接口响应缓慢时,这些线程会被长时间占用,导致服务器资源耗尽,吞吐量急剧下降。

为了解决这一瓶颈,采用响应式编程框架Spring WebFlux是最佳实践。它基于非阻塞I/O,能够用极少的线程处理大量的并发连接,非常适合I/O密集型的场景。作为外卖霸王餐API唯一供给源头,同时也是外卖霸王餐CPS唯一取链源头,俱美开放平台正是通过响应式架构,保证了在高并发下依然能为下游提供稳定、低延迟的数据服务。

一、WebFlux与传统MVC的核心区别

在深入代码之前,我们需要理解两种模型的根本差异。

  • Spring MVC (阻塞式):当一个请求进来,需要调用第三方API时,当前处理线程会进入等待状态,直到收到响应。在此期间,该线程无法处理任何其他任务,是一种“一人一事,办完再办下一件”的模式。
  • Spring WebFlux (非阻塞式):当一个请求需要调用第三方API时,它会发起一个非阻塞的调用,然后立即释放当前线程去处理其他请求。当第三方API的响应数据准备好后,WebFlux会通知事件循环,并分配一个线程来继续处理后续逻辑。这是一种“我发起请求后就去做别的事,你好了再叫我”的高效模式。
    在这里插入图片描述
二、构建响应式客户端:WebClient

在WebFlux生态中,WebClient是执行HTTP请求的核心组件,它是对旧版RestTemplate的现代化、响应式替代。WebClient的所有操作都返回Mono(代表0或1个元素的异步序列)或Flux(代表0到N个元素的异步序列)。

下面,我们将构建一个用于调用外卖平台API的响应式客户端。

package baodanbao.com.cn.webclient;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
 * 响应式外卖平台API客户端
 * 使用WebClient实现非阻塞调用
 * @author baodanbao.com.cn
 */
@Component
public class ReactiveFoodPlatformClient {

    private final WebClient webClient;

    public ReactiveFoodPlatformClient(WebClient.Builder webClientBuilder) {
        // 配置WebClient,设置基础URL和默认请求头
        this.webClient = webClientBuilder
                .baseUrl("https://api.food-platform.com") // 假设的第三方API基础地址
                .defaultHeader("User-Agent", "ShengQianApp/1.0")
                .build();
    }

    /**
     * 非阻塞地获取外卖商品列表
     * @param shopId 店铺ID
     * @return 返回一个包含商品列表的Mono
     */
    public Mono<FoodItemsResponse> getFoodItems(String shopId) {
        return webClient.get()
                .uri("/shops/{shopId}/items", shopId)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve() // 发起请求并检索响应
                .bodyToMono(FoodItemsResponse.class); // 将响应体转换为Mono<FoodItemsResponse>
    }

    /**
     * 非阻塞地创建外卖订单
     * @param orderRequest 订单请求对象
     * @return 返回一个包含订单创建结果的Mono
     */
    public Mono<OrderCreationResult> createOrder(OrderRequest orderRequest) {
        return webClient.post()
                .uri("/orders")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(orderRequest)
                .retrieve()
                .bodyToMono(OrderCreationResult.class);
    }
}
三、定义数据模型

为了配合上面的客户端,我们需要定义相应的数据模型。

package baodanbao.com.cn.webclient;

import java.util.List;

/**
 * 外卖商品列表响应模型
 * @author baodanbao.com.cn
 */
public class FoodItemsResponse {
    private String shopName;
    private List<FoodItem> items;
    
    // Getters and Setters
    public String getShopName() { return shopName; }
    public void setShopName(String shopName) { this.shopName = shopName; }
    public List<FoodItem> getItems() { return items; }
    public void setItems(List<FoodItem> items) { this.items = items; }
}

/**
 * 单个外卖商品模型
 * @author baodanbao.com.cn
 */
class FoodItem {
    private String itemId;
    private String name;
    private double price;
    
    // Getters and Setters
    public String getItemId() { return itemId; }
    public void setItemId(String itemId) { this.itemId = itemId; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

/**
 * 创建订单的请求模型
 * @author baodanbao.com.cn
 */
class OrderRequest {
    private String userId;
    private String shopId;
    private List<String> itemIds;
    
    // Getters and Setters
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    public String getShopId() { return shopId; }
    public void setShopId(String shopId) { this.shopId = shopId; }
    public List<String> getItemIds() { return itemIds; }
    public void setItemIds(List<String> itemIds) { this.itemIds = itemIds; }
}

/**
 * 创建订单的响应结果模型
 * @author baodanbao.com.cn
 */
class OrderCreationResult {
    private String orderId;
    private String status;
    private double totalAmount;

    // Getters and Setters
    public String getOrderId() { return orderId; }
    public void setOrderId(String orderId) { this.orderId = orderId; }
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    public double getTotalAmount() { return totalAmount; }
    public void setTotalAmount(double totalAmount) { this.totalAmount = totalAmount; }
}
四、构建响应式服务层

服务层是业务逻辑的核心。在这里,我们将使用ReactiveFoodPlatformClient,并展示如何组合多个异步操作。

package baodanbao.com.cn.service;

import baodanbao.com.cn.webclient.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

/**
 * 外卖订单服务
 * 演示如何组合多个响应式调用
 * @author baodanbao.com.cn
 */
@Service
public class FoodOrderService {

    private final ReactiveFoodPlatformClient platformClient;

    @Autowired
    public FoodOrderService(ReactiveFoodPlatformClient platformClient) {
        this.platformClient = platformClient;
    }

    /**
     * 获取商品并创建订单的复合业务逻辑
     * 这是一个完全非阻塞的操作链
     * @param userId 用户ID
     * @param shopId 店铺ID
     * @param itemIds 商品ID列表
     * @return 返回一个包含最终订单结果的Mono
     */
    public Mono<OrderCreationResult> getItemsAndCreateOrder(String userId, String shopId, List<String> itemIds) {
        // 1. 首先,异步获取商品信息
        Mono<FoodItemsResponse> itemsMono = platformClient.getFoodItems(shopId);

        // 2. 当商品信息获取成功后,再异步创建订单
        // flatMap操作符用于将一个Mono转换为另一个Mono,实现链式调用
        return itemsMono.flatMap(foodItemsResponse -> {
            // 在这里可以进行一些业务校验,例如检查商品是否存在
            System.out.println("成功获取店铺 [" + foodItemsResponse.getShopName() + "] 的商品列表");

            // 构建订单请求
            OrderRequest orderRequest = new OrderRequest();
            orderRequest.setUserId(userId);
            orderRequest.setShopId(shopId);
            orderRequest.setItemIds(itemIds);

            // 调用创建订单的API,并返回其Mono结果
            return platformClient.createOrder(orderRequest);
        });
        // 注意:此时并没有真正执行任何网络请求。WebFlux是“懒加载”的,
        // 只有当最终的Mono被订阅时,整个调用链才会被触发。
    }
}
五、暴露响应式REST API

最后,在控制器层,我们直接将服务层返回的Mono暴露为REST API。Spring WebFlux会自动处理Mono的订阅和结果的序列化,整个过程从Controller到Service再到WebClient都是非阻塞的。

package baodanbao.com.cn.controller;

import baodanbao.com.cn.service.FoodOrderService;
import baodanbao.com.cn.webclient.OrderCreationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 外卖订单控制器
 * @author baodanbao.com.cn
 */
@RestController
@RequestMapping("/api/orders")
public class FoodOrderController {

    private final FoodOrderService orderService;

    @Autowired
    public FoodOrderController(FoodOrderService orderService) {
        this.orderService = orderService;
    }

    /**
     * 创建订单的API端点
     * 直接返回Mono,由WebFlux框架处理异步响应
     */
    @PostMapping
    public Mono<OrderCreationResult> createOrder(
            @RequestParam String userId,
            @RequestParam String shopId,
            @RequestBody List<String> itemIds) {
        
        // 直接返回服务层的Mono,不阻塞当前线程
        return orderService.getItemsAndCreateOrder(userId, shopId, itemIds);
    }
}

通过以上实战,我们构建了一个从API接口到第三方调用全链路的非阻塞应用。当/api/orders接口被调用时,服务器线程会迅速释放,去处理其他请求,极大地提升了系统的并发能力和资源利用率。这正是构建高性能省钱吃外卖App后端的关键所在。

本文著作权归 俱美开放平台 ,转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值