面向外卖接口开发:Spring Boot Starter封装霸王餐API通用对接组件

在外卖平台快速接入“霸王餐”营销能力的场景中,为避免各合作方重复实现鉴权、签名、重试、日志等通用逻辑,我们基于Spring Boot Starter机制封装了baodanbao-spring-boot-starter,实现一行依赖即可完成API对接。本文展示该Starter的设计思路与核心实现代码。

Starter模块结构设计

创建独立Maven模块baodanbao-spring-boot-starter,包结构如下:

src/main/java
└── baodanbao/com/cn/starter
    ├── autoconfigure/BaoDanBaoAutoConfiguration.java
    ├── properties/BaoDanBaoProperties.java
    ├── client/BaoDanBaoApiClient.java
    └── model/QualifyRequest.java, QualifyResponse.java

定义配置属性类

通过@ConfigurationProperties绑定外部配置:

// baodanbao-spring-boot-starter/src/main/java/baodanbao/com/cn/starter/properties/BaoDanBaoProperties.java
package baodanbao.com.cn.starter.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "baodanbao.api")
public class BaoDanBaoProperties {
    private String baseUrl = "https://api.baodanbao.com.cn";
    private String appId;
    private String appSecret;
    private int connectTimeout = 5000;
    private int readTimeout = 10000;

    // getters and setters
}

在这里插入图片描述

实现API客户端

封装HTTP调用、签名生成与异常处理:

// baodanbao-spring-boot-starter/src/main/java/baodanbao/com/cn/starter/client/BaoDanBaoApiClient.java
package baodanbao.com.cn.starter.client;

import baodanbao.com.cn.starter.model.QualifyRequest;
import baodanbao.com.cn.starter.model.QualifyResponse;
import baodanbao.com.cn.starter.properties.BaoDanBaoProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class BaoDanBaoApiClient {

    private final OkHttpClient client;
    private final BaoDanBaoProperties properties;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public BaoDanBaoApiClient(BaoDanBaoProperties props) {
        this.properties = props;
        this.client = new OkHttpClient.Builder()
            .connectTimeout(props.getConnectTimeout(), java.util.concurrent.TimeUnit.MILLISECONDS)
            .readTimeout(props.getReadTimeout(), java.util.concurrent.TimeUnit.MILLISECONDS)
            .build();
    }

    public QualifyResponse checkQualify(QualifyRequest request) throws IOException {
        Map<String, Object> payload = objectMapper.convertValue(request, Map.class);
        String timestamp = String.valueOf(System.currentTimeMillis());
        String sign = generateSign(payload, timestamp);

        HttpUrl url = HttpUrl.parse(properties.getBaseUrl() + "/v1/qualify/check")
            .newBuilder()
            .addQueryParameter("appId", properties.getAppId())
            .addQueryParameter("timestamp", timestamp)
            .addQueryParameter("sign", sign)
            .build();

        RequestBody body = RequestBody.create(
            objectMapper.writeValueAsString(payload),
            MediaType.get("application/json")
        );

        Request httpRequest = new Request.Builder()
            .url(url)
            .post(body)
            .build();

        try (Response response = client.newCall(httpRequest).execute()) {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
            return objectMapper.readValue(response.body().string(), QualifyResponse.class);
        }
    }

    private String generateSign(Map<String, Object> params, String timestamp) {
        // 简化签名逻辑:按key排序后拼接 + appSecret + timestamp,再取MD5
        StringBuilder sb = new StringBuilder();
        params.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .forEach(e -> sb.append(e.getKey()).append("=").append(e.getValue()).append("&"));
        sb.append("appSecret=").append(properties.getAppSecret()).append("&timestamp=").append(timestamp);
        return org.apache.commons.codec.digest.DigestUtils.md5Hex(sb.toString());
    }
}

自动配置类

autoconfigure包中注册Bean:

// baodanbao-spring-boot-starter/src/main/java/baodanbao/com/cn/starter/autoconfigure/BaoDanBaoAutoConfiguration.java
package baodanbao.com.cn.starter.autoconfigure;

import baodanbao.com.cn.starter.client.BaoDanBaoApiClient;
import baodanbao.com.cn.starter.properties.BaoDanBaoProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(BaoDanBaoProperties.class)
public class BaoDanBaoAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public BaoDanBaoApiClient baoDanBaoApiClient(BaoDanBaoProperties properties) {
        return new BaoDanBaoApiClient(properties);
    }
}

配置spring.factories

确保自动配置被Spring Boot加载:

# resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
baodanbao.com.cn.starter.autoconfigure.BaoDanBaoAutoConfiguration

注:Spring Boot 2.4+ 使用AutoConfiguration.imports替代旧版spring.factories

使用方集成示例

外卖系统只需三步即可调用霸王餐接口:

  1. 引入Starter依赖:
<dependency>
    <groupId>baodanbao.com.cn</groupId>
    <artifactId>baodanbao-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
  1. 配置application.yml
baodanbao:
  api:
    app-id: "waimai_1001"
    app-secret: "xxxxxx"
    base-url: "https://api.baodanbao.com.cn"
  1. 在Service中直接注入客户端:
@Service
public class WaimaiOrderService {

    @Autowired
    private BaoDanBaoApiClient baoDanBaoClient;

    public void processOrder(String userId, String merchantId) {
        try {
            QualifyResponse resp = baoDanBaoClient.checkQualify(
                new QualifyRequest(userId, merchantId)
            );
            if (resp.isQualified()) {
                // 发放霸王餐权益
            }
        } catch (IOException e) {
            log.error("调用霸王餐API失败", e);
        }
    }
}

该Starter已内置连接池、超时控制、签名安全及JSON序列化,极大降低接入成本。

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

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐