网站首页 > 基础教程 正文
在日常开发中,总会接触到各种接口。前后端数据传输接口,第三方业务平台接口。一个平台的前后端数据传输接口一般都会在内网环境下通信,而且会使用安全框架,所以安全性可以得到很好的保护。涉及到开放平台接口应当如何设计?我们应该考虑哪些问题?
主要从三个方面来设计一个相对靠谱的API接口。
一、 安全性问题
安全性问题是一个接口必须要保证的规范。如果接口保证不了安全性,那么你的接口相当于直接暴露在公网环境中任人蹂躏。
- 身份验证:开放平台需要验证调用方的身份,确保其具有访问该接口的权限。通常采用应用程序标识(App ID)和应用程序密钥(App Key)进行身份验证。
- 防重放攻击:开放平台接口通常会包含一个时间戳和一个随机字符串,以及一个数字签名(signature),用于验证请求的完整性和确保请求不会被重复执行。
- 防止恶意代码注入:对于一些涉及到用户输入的接口,开放平台需要对输入参数进行严格的过滤和验证,防止SQL注入、跨站点脚本攻击等安全问题。
以下是一个使用Spring Boot实现的开放平台接口安全性示例:
@RestController
public class OpenApiController {
private static final String APP_ID = "your_app_id";
private static final String APP_KEY = "your_app_key";
private static final long TIMESTAMP_VALIDITY_IN_SECONDS = 60; // 60 seconds
@GetMapping("/api/data")
public String getData(@RequestParam("appId") String appId,
@RequestParam("timestamp") long timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("signature") String signature) {
// Step 1: Check App ID and App Key
if (!APP_ID.equals(appId)) {
return "Invalid app ID";
}
String expectedSignature = generateSignature(APP_KEY, timestamp, nonce);
if (!expectedSignature.equals(signature)) {
return "Invalid signature";
}
// Step 2: Check timestamp validity
long currentTimestamp = System.currentTimeMillis() / 1000;
if (Math.abs(currentTimestamp - timestamp) > TIMESTAMP_VALIDITY_IN_SECONDS) {
return "Invalid timestamp";
}
// Step 3: Process the request
return "Data";
}
private String generateSignature(String appKey, long timestamp, String nonce) {
String s = appKey + timestamp + nonce;
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] digest = messageDigest.digest(s.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(digest).toLowerCase();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
在此示例中,接口需要包含四个参数:appId、timestamp、nonce和signature。其中,appId和timestamp是必需的,nonce和signature是可选的。要使用接口,调用方需要提供这四个参数以及对应的值。服务端首先检查appId和App Key是否正确,然后使用提供的timestamp、nonce和App Key生成数字签名,与提供的signature进行比较,以确保请求的完整性。最后,服务端还检查提供的timestamp是否在有效时间范围内。如果所有检查都通过,服务端将执行请求的操作。
二 、幂等性问题
幂等性指的是,无论请求被调用多少次,结果都是相同的。如果一个接口不具有幂等性,那么在接口调用失败的情况下,会出现重复的数据和不一致的状态。
解决幂等性问题的一种常用方式是使用请求唯一标识符,也称为请求ID。每个请求都应该包含一个唯一标识符,可以是随机生成的字符串、UUID、时间戳等。服务端在接收到请求时,先根据请求ID查询请求是否已经处理过,如果已经处理过,则直接返回之前的结果,否则进行正常处理。
以下是使用请求唯一标识符实现幂等性的Java代码示例:
@RestController
public class DemoController {
private Map<String, Object> processedRequests = new HashMap<>();
@PostMapping("/api/demo")
public ResponseEntity<String> handleDemoRequest(@RequestBody DemoRequest request) {
String requestId = request.getRequestId();
if (processedRequests.containsKey(requestId)) {
// 请求已经被处理过,直接返回之前的结果
return new ResponseEntity<>(processedRequests.get(requestId), HttpStatus.OK);
}
// 处理请求
String response = handleRequest(request);
// 将请求和响应存储到缓存中,有效期为一定时间
processedRequests.put(requestId, response);
scheduleCacheEviction(requestId, EXPIRATION_TIME);
return new ResponseEntity<>(response, HttpStatus.OK);
}
private void scheduleCacheEviction(String requestId, long expirationTime) {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.schedule(() -> {
processedRequests.remove(requestId);
}, expirationTime, TimeUnit.SECONDS);
}
}
在上面的代码中,handleDemoRequest方法接收一个DemoRequest对象作为参数,其中包含了请求ID。如果processedRequests缓存中已经存在该请求ID对应的响应结果,则直接返回之前的结果;否则,进行正常处理,并将请求ID和响应结果存储到缓存中,并设定过期时间。过期时间可以根据实际需要进行调整,一般应该设置为接口处理完成后一定时间内的有效期。在本例中,使用了Java自带的ScheduledExecutorService来定时清理过期的缓存。
这样实现的好处是,不需要对接口进行特殊的限制,只需要保证每个请求都有唯一的请求ID即可。当同样的请求被重复调用时,服务端会返回之前的响应结果,从而保证了接口的幂等性。
三 、数据规范问题
在开放平台接口设计中,数据规范非常重要,因为数据规范的缺失或不一致可能会导致接口的调用失败,甚至是数据的丢失或损坏。下面是几个常见的数据规范问题:
数据格式
在接口设计中,数据格式应该是一致的,否则会导致解析错误。比如日期格式,应该使用标准的 ISO 格式,如 yyyy-MM-dd HH:mm:ss。
数据长度
接口数据的长度限制应该在一定的范围内,过长或过短都可能会导致问题。例如,手机号码的长度应该在11位。
数据类型
接口中的数据类型应该与 API 文档中规定的数据类型相匹配。如果类型不匹配,会导致解析错误。例如,如果 API 文档中规定的数据类型是整数,那么在接口中传递字符串类型的数据就是不合规的。
枚举类型
在接口中使用枚举类型可以确保传递的数据符合特定的值域。例如,一个订单状态字段可以定义为枚举类型,只能传递已定义的状态值,这可以减少潜在的错误。
下面是一个示例代码,说明如何在接口设计中使用枚举类型:
kotlinCopy code
public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
DELIVERED,
CANCELLED;
}
public class Order {
private String orderId;
private OrderStatus status;
// Getter and setter methods// ...
}
在这个示例中,OrderStatus 是一个枚举类型,表示订单的状态。在 Order 类中,status 字段使用了这个枚举类型,确保只能传递已定义的状态值。这样可以避免传递不合规的数据,确保数据规范性。
除此之外,在接口设计中,还可以使用数据校验框架,如 Hibernate Validator、Spring Validator 等来确保数据规范性,避免数据异常情况的出现。
数据响应
状态码定义和统一状态响应规范非常重要,能够让接口的使用更加简单明了,减少沟通成本和错误的发生。一般来说,常见的状态码定义如下:
- 200 OK:表示请求成功,服务器已经正确处理了请求。
- 201 Created:表示请求已经被成功处理,并且创建了新的资源。
- 204 No Content:表示请求已经被成功处理,但是没有返回任何内容。
- 400 Bad Request:表示请求格式不正确,服务器无法处理请求。
- 401 Unauthorized:表示没有权限访问该资源,需要进行身份验证。
- 403 Forbidden:表示请求被拒绝,服务器拒绝提供该资源。
- 404 Not Found:表示请求的资源不存在。
- 500 Internal Server Error:表示服务器发生了错误,无法处理请求。
除了定义状态码之外,还需要规范统一的状态响应格式,例如:
{
"code": 200,
"message": "success",
"data": {}
}
其中,code表示状态码,message表示状态信息,data表示响应数据。在具体实现时,可以将这个格式作为返回值,在接口返回时进行封装。下面是一个简单的实例:
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// getter and setter methods
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}
在上面的实例中,定义了一个ApiResponse类,其中包含状态码、状态信息和响应数据三个字段。同时,定义了两个静态方法success和error,用于返回成功和失败的响应。具体使用时,可以这样调用:
// 成功的响应
ApiResponse<User> response = ApiResponse.success(user);
// 失败的响应
ApiResponse<Void> response = ApiResponse.error(404, "User not found");
通过定义状态码和统一的状态响应格式,可以让接口的使用更加规范化和标准化,提高接口的可用性和易用性。
四、总结
本篇文章从安全性、幂等性、数据规范等方面讨论了API设计规范。除此之外,一个好的API还少不了一个优秀的接口文档。接口文档的可读性非常重要,虽然很多程序员都不喜欢写文档,而且不喜欢别人不写文档。为了不增加程序员的压力,推荐使用swagger或其他接口管理工具,通过简单配置,就可以在开发中测试接口的连通性,上线后也可以生成离线文档用于管理API。
- 上一篇: 云原生-Quarkus上下文依赖注入参考
- 下一篇: 一起看 I/O | Jetpack 组件的新特性
猜你喜欢
- 2024-11-19 1-0 Protobuf通信协议.proto文件编写
- 2024-11-19 阿里P8资深架构师耗时一年整理19年Java工程师成神之路
- 2024-11-19 Java修炼终极指南:83. 声明一个Java记录
- 2024-11-19 Effective Java 3rd(Java高效编程)技术要点
- 2024-11-19 Spring源码系列(二)
- 2024-11-19 Java 14 发布了,快来了解下吧
- 2024-11-19 Spring技巧:深入研究Java 14和SpringBoot
- 2024-11-19 IntelliJ IDEA 2023.2 最新变化
- 2024-11-19 数据结构与算法 #18 下跳棋,极富想象力的同向双指针模拟
- 2024-11-19 Spring Bean生命周期你除了会背八股文面试,真的会用了吗?
- 最近发表
- 标签列表
-
- jsp (69)
- gitpush (78)
- gitreset (66)
- python字典 (67)
- dockercp (63)
- gitclone命令 (63)
- dockersave (62)
- linux命令大全 (65)
- pythonif (86)
- location.href (69)
- dockerexec (65)
- tail-f (79)
- queryselectorall (63)
- location.search (79)
- bootstrap教程 (74)
- deletesql (62)
- linuxgzip (68)
- 字符串连接 (73)
- html标签 (69)
- c++初始化列表 (64)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)