Java开发一个接口提供给第三方调用

1. 环境

基于SpringBoot编写一个接口,提供给第三方调用。类似于我们使用阿里的语音识别功能,我们可以调用阿里封装好的api,也就是通过发送HTTP请求的方式来做语音识别。本篇文章主要记录在SpringBoot中我们是如何开发接口并让别人可以安全调用的。

使用到的依赖:pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.lukeewin</groupId>
    <artifactId>Signature</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Signature</name>
    <description>Signature</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2. 加密算法的选择

使用MD5这种算法加密是不太安全的,所以这里我们使用hash算法中的HmacSHA256加密算法来生成签名,当我们请求接口时,我们使用把签名和时间戳带上,为啥还要带上时间戳呢,是因为我们之后要控制签名的过期时间需要根据这个前端传递过来的时间戳来计算过期时间。

下面是加密工具类:

java 复制代码
public class SignatureUtil {
    public static String getSignature(String timestamp, String apiKey, String apiSecret) {
        // 构建签名字符串
        String signatureString = apiKey + timestamp;

        String signature = null;

        // 计算签名
        try {
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKey);
            byte[] signatureBytes = sha256Hmac.doFinal(signatureString.getBytes(StandardCharsets.UTF_8));
            signature = Base64.getEncoder().encodeToString(signatureBytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
        return signature;
    }
}

3. 编写一个接口

这里简单编写一个音频转码的接口,来模拟我们开发接口的整个过程。

java 复制代码
@RestController
public class TransferController {

    @RequestMapping("/transfer")
    public BaseResponse transfer() {
        return BaseResponse.success("转码成功");
    }

    @RequestMapping("/ban")
    public BaseResponse ban() {
        return BaseResponse.error(ErrorCode.VERIFY_NO_PASS);
    }
}

4. 自定义一个拦截器

自定义拦截器,对全面请求进行拦截判断是否传递了签名和时间戳,并且判断传递过来的签名和后端计算出来的签名一不一致,还需判断传递到后端时这个签名有没有过期,如果上面这些条件有一个不成立就进行拦截,否则放行。

java 复制代码
@Component
public class SignatureInterceptor implements HandlerInterceptor {
    @Value("${apiKey}")
    private String apiKey;

    @Value("${apiSecret}")
    private String apiSecret;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String sign = request.getParameter("sign");
        String timestamp = request.getParameter("timestamp");
        if (StringUtils.isNotBlank(sign) && StringUtils.isNotBlank(timestamp)) {
            String signature = SignatureUtil.getSignature(timestamp, apiKey, apiSecret);
            if (StringUtils.isNotBlank(signature) && signature.equals(sign) && System.currentTimeMillis() - Long.parseLong(timestamp) < 50 * 1000) {
                return true;
            } else {
                request.getRequestDispatcher("/ban").forward(request, response);
                return false;
            }
        } else {
            request.getRequestDispatcher("/ban").forward(request, response);
            return false;
        }
    }
}

注意点:

  1. 必须要把该类交给Spring IOC容器进行管理,也就是需要在类上面添加一个注解@Component
  2. 拦截后需要给调用方一个提示,否则调用方不知道是否被拦截,所以这里需要使用request.getRequestDispatcher("/ban").forward(request, response);
  3. 必须要放行拦截的URL,如果不放行,会产生死循环,在这里也就是需要放行/ban接口

5. 编写一个拦截器的配置类

编写拦截器配置类,把自定义的拦截器添加到配置类中。

java 复制代码
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Resource
    private SignatureInterceptor signatureInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signatureInterceptor).addPathPatterns("/**").excludePathPatterns("/ban");
    }
}

注意点:

  1. 必须要有@Configuration注解
  2. 不能使用new 自定义拦截器类的方式添加进来,必须要使用注入的方式注入进来。也就是不能写成这样registry.addInterceptor(new SignatureInterceptor()).addPathPatterns("/**")

6. 统一接口的响应格式

创建两个工具类,一个是响应基类,一个是错误类。

响应基类:BaseResponse

java 复制代码
@Data
public class BaseResponse<T> implements Serializable {
    private static final long serialVersionUID = 4L;
    private Integer code;
    private String message;
    private Long timestamp = System.currentTimeMillis();
    private T data;

    public static <T> BaseResponse<T> success(T data) {
        BaseResponse<T> resultData = new BaseResponse<>();
        resultData.setCode(200);
        resultData.setMessage("OK");
        resultData.setData(data);
        return resultData;
    }

    public static BaseResponse error(ErrorCode errorCode) {
        BaseResponse resultData = new BaseResponse();
        resultData.setCode(errorCode.getCode());
        resultData.setMessage(errorCode.getMessage());
        return resultData;
    }
}

这里用到了@Data注解,是lombok提供的一个注解,所以你需要在pom.xml中引入lombok依赖。

编写错误码类:ErrorCode

java 复制代码
public enum ErrorCode {
    VERIFY_NO_PASS(300, "签名验证未通过");
    private final Integer code;
    private final String message;

    ErrorCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

7. 配置文件

在自定义拦截器中,我们通过@Value注解从项目的配置文件application.yml中获取apiKeyapiSecret

application.yml文件如下:

yaml 复制代码
apiKey: dhkadj123fda
apiSecret: hgjdakf12314sdf

对应的视频教程已经上传到B站中,如果不喜欢看文字内容,也可以看视频

相关推荐
迷迭所归处32 分钟前
C++ —— 以真我之名 如飞花般绚丽 - 智能指针
开发语言·c++
ygl615037334 分钟前
Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统
java·spring boot·vue
Code哈哈笑37 分钟前
【Java 学习】构造器、static静态变量、static静态方法、static构造器、
java·开发语言·学习
是老余38 分钟前
Java三大特性:封装、继承、多态【详解】
java·开发语言
鸽鸽程序猿39 分钟前
【JavaEE】Maven的介绍及配置
java·java-ee·maven
小馒头学python40 分钟前
【Python爬虫五十个小案例】爬取豆瓣电影Top250
开发语言·爬虫·python
尘浮生2 小时前
Java项目实战II基于微信小程序的南宁周边乡村游平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·微信小程序·小程序·maven
wangjing_05224 小时前
C语言练习.if.else语句.strstr
c语言·开发语言
Tony_long74834 小时前
Python学习——字符串操作方法
开发语言·c#
SoraLuna4 小时前
「Mac玩转仓颉内测版26」基础篇6 - 字符类型详解
开发语言·算法·macos·cangjie