135,486
社区成员
发帖
与我相关
我的任务
分享一、总体对比
| 维度 | SDK接口 | 普通Java程序 |
| 使用方式 | 被其他程序调用(API调用) | 独立运行(main方法启动) |
| 交互方式 | 网络通信(HTTP/RPC) | 本地方法调用 |
| 数据格式 | JSON/XML/Protobuf等标准格式 | Java对象直接传递 |
| 状态管理 | 无状态(Stateless)设计 | 可以有状态 |
| 并发处理 | 需要处理高并发 | 通常是单用户使用 |
| 安全要求 | 需要鉴权、限流、防攻击 | 相对较低 |
| 文档要求 | 必须提供API文档 | 通常只需要代码注释 |
二、理解
// 普通Java程序:就像自己做饭
public class CookDinner {
public static void main(String[] args) {
// 自己切菜、炒菜、吃饭
Vegetables veggies = new Vegetables();
veggies.wash();
veggies.cut();
Rice rice = new Rice();
rice.cook();
System.out.println("开饭啦!");
}
}
// SDK接口:就像叫外卖
@RestController
public class TakeoutAPI {
@PostMapping("/order")
public ResponseEntity<Food> orderFood(@RequestBody OrderRequest request) {
// 1. 验证用户身份
if (!validateUser(request.getUserId())) {
return ResponseEntity.status(401).build();
}
// 2. 检查库存
if (!checkInventory(request.getFoodId())) {
return ResponseEntity.status(400).body("库存不足");
}
// 3. 处理订单(可能调用厨房系统)
Food food = kitchenService.cook(request);
// 4. 返回标准化的响应
return ResponseEntity.ok(food);
}
}
三、Java接口编写标准规范
package com.example.api;
import io.swagger.annotations.*;
import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
/**
* 用户管理接口
* @version 1.0
* @since 2024-01-01
*/
@RestController
@RequestMapping("/api/v1/users")
@Api(tags = "用户管理", description = "用户相关的CRUD操作")
public class UserController {
/**
* 创建用户
* @param request 创建用户请求
* @return 创建的用户信息
*/
@PostMapping
@ApiOperation(value = "创建用户", notes = "创建一个新的用户账号")
@ApiResponses({
@ApiResponse(code = 201, message = "创建成功"),
@ApiResponse(code = 400, message = "请求参数错误"),
@ApiResponse(code = 409, message = "用户已存在")
})
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody CreateUserRequest request) {
// 业务逻辑
UserResponse response = userService.create(request);
return ResponseEntity.status(201).body(response);
}
/**
* 查询用户(分页)
* @param page 页码(从0开始)
* @param size 每页大小
* @return 用户列表
*/
@GetMapping
@ApiOperation("分页查询用户")
public ResponseEntity<PageResponse<UserResponse>> listUsers(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size) {
PageResponse<UserResponse> users = userService.list(page, size);
return ResponseEntity.ok(users);
}
/**
* 获取用户详情
* @param userId 用户ID
* @return 用户详情
*/
@GetMapping("/{userId}")
@ApiOperation("获取用户详情")
public ResponseEntity<UserDetailResponse> getUser(
@PathVariable @ApiParam("用户ID") String userId) {
UserDetailResponse user = userService.getById(userId);
return ResponseEntity.ok(user);
}
/**
* 更新用户
* @param userId 用户ID
* @param request 更新信息
* @return 更新后的用户
*/
@PutMapping("/{userId}")
@ApiOperation("更新用户信息")
public ResponseEntity<UserResponse> updateUser(
@PathVariable String userId,
@Valid @RequestBody UpdateUserRequest request) {
UserResponse user = userService.update(userId, request);
return ResponseEntity.ok(user);
}
/**
* 删除用户
* @param userId 用户ID
* @return 无内容
*/
@DeleteMapping("/{userId}")
@ApiOperation("删除用户")
public ResponseEntity<Void> deleteUser(@PathVariable String userId) {
userService.delete(userId);
return ResponseEntity.noContent().build();
}
}
3.2、 请求/响应对象规范
package com.example.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.validation.constraints.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import java.util.List;
/**
* 创建用户请求
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Min(value = 1, message = "年龄必须大于0")
@Max(value = 150, message = "年龄必须小于150")
private Integer age;
private String address;
private List<@NotBlank String> tags;
}
/**
* 用户响应
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
private String id;
private String username;
private String email;
private String phone;
private Integer age;
private String status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
}
/**
* 分页响应
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResponse<T> {
private List<T> content; // 数据列表
private int page; // 当前页码
private int size; // 每页大小
private long totalElements; // 总记录数
private int totalPages; // 总页数
private boolean first; // 是否是第一页
private boolean last; // 是否是最后一页
}
3.3、统一异常处理
package com.example.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
ErrorResponse response = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("参数验证失败")
.message("请求参数不符合要求")
.details(errors)
.path(getCurrentPath())
.build();
return ResponseEntity.badRequest().body(response);
}
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
log.error("业务异常: {}", ex.getMessage());
ErrorResponse response = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(ex.getCode())
.error("业务处理失败")
.message(ex.getMessage())
.path(getCurrentPath())
.build();
return ResponseEntity.status(ex.getCode()).body(response);
}
/**
* 处理系统异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleSystemException(Exception ex) {
log.error("系统异常: ", ex);
ErrorResponse response = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.error("系统内部错误")
.message("服务器处理请求时发生错误")
.path(getCurrentPath())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
private String getCurrentPath() {
// 获取当前请求路径
return "";
}
}
/**
* 错误响应
*/
@Data
@Builder
public class ErrorResponse {
private LocalDateTime timestamp; // 错误时间
private int status; // HTTP状态码
private String error; // 错误类型
private String message; // 错误信息
private Map<String, String> details;// 详细错误
private String path; // 请求路径
}
/**
* 业务异常
*/
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(String message) {
this(400, message);
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
四、API文档规范(Swagger/OpenAPI)
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.Arrays;
import java.util.List;
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(PathSelectors.any())
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("文件上传服务API文档")
.description("提供文件上传、下载、管理等接口")
.version("1.0.0")
.contact(new Contact("技术团队", "https://example.com", "dev@example.com"))
.license("Apache 2.0")
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
.build();
}
private List<SecurityScheme> securitySchemes() {
return Arrays.asList(
new ApiKey("Authorization", "Authorization", "header")
);
}
private List<SecurityContext> securityContexts() {
return Arrays.asList(
SecurityContext.builder()
.securityReferences(securityReferences())
.operationSelector(operation ->
operation.requestMappingPattern().startsWith("/api/"))
.build()
);
}
private List<SecurityReference> securityReferences() {
return Arrays.asList(
new SecurityReference("Authorization",
new AuthorizationScope[]{
new AuthorizationScope("global", "访问所有接口")
}
)
);
}
}
五、接口版本管理规范
/**
* API版本管理示例
*/
@RestController
public class VersionedApiController {
// 方式1:URL路径版本
@GetMapping("/v1/users")
public ResponseEntity<List<User>> getUsersV1() {
return ResponseEntity.ok(userService.getUsersV1());
}
@GetMapping("/v2/users")
public ResponseEntity<List<UserV2>> getUsersV2() {
return ResponseEntity.ok(userService.getUsersV2());
}
// 方式2:请求头版本
@GetMapping(value = "/users", headers = "API-VERSION=1")
public ResponseEntity<List<User>> getUsersHeaderV1() {
return ResponseEntity.ok(userService.getUsersV1());
}
@GetMapping(value = "/users", headers = "API-VERSION=2")
public ResponseEntity<List<UserV2>> getUsersHeaderV2() {
return ResponseEntity.ok(userService.getUsersV2());
}
// 方式3:内容类型版本
@GetMapping(value = "/users", produces = "application/vnd.company.v1+json")
public ResponseEntity<List<User>> getUsersContentV1() {
return ResponseEntity.ok(userService.getUsersV1());
}
@GetMapping(value = "/users", produces = "application/vnd.company.v2+json")
public ResponseEntity<List<UserV2>> getUsersContentV2() {
return ResponseEntity.ok(userService.getUsersV2());
}
}
六、接口安全规范
package com.example.security;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/secure")
public class SecureApiController {
/**
* 需要管理员权限
*/
@PostMapping("/admin/operation")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Result> adminOperation(@Valid @RequestBody AdminRequest request) {
// 只有管理员才能调用
return ResponseEntity.ok(adminService.execute(request));
}
/**
* 需要用户权限,且只能操作自己的数据
*/
@PutMapping("/users/{userId}")
@PreAuthorize("authentication.principal.id == #userId")
public ResponseEntity<UserResponse> updateUser(
@PathVariable String userId,
@Valid @RequestBody UpdateUserRequest request) {
// 只能更新自己的信息
return ResponseEntity.ok(userService.update(userId, request));
}
/**
* 接口限流
*/
@GetMapping("/limited-api")
@RateLimiter(key = "#userId", rate = 10, capacity = 10)
public ResponseEntity<String> limitedApi(@RequestParam String userId) {
return ResponseEntity.ok("success");
}
}
/**
* 自定义注解实现限流
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
String key() default "";
int rate() default 10; // 速率
int capacity() default 10; // 容量
}
七、日志记录规范
package com.example.aspect;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
@Slf4j
@Aspect
@Component
public class ApiLogAspect {
private final ObjectMapper objectMapper = new ObjectMapper();
@Around("@within(org.springframework.web.bind.annotation.RestController)")
public Object logApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
// 生成请求ID
String requestId = UUID.randomUUID().toString();
// 获取请求信息
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
long startTime = System.currentTimeMillis();
try {
// 记录请求日志
log.info("Request [{}]开始: method={}, url={}, ip={}, params={}",
requestId,
request.getMethod(),
request.getRequestURL(),
request.getRemoteAddr(),
objectMapper.writeValueAsString(joinPoint.getArgs())
);
// 执行目标方法
Object result = joinPoint.proceed();
// 记录响应日志
long costTime = System.currentTimeMillis() - startTime;
log.info("Request [{}]完成: 耗时={}ms, 响应={}",
requestId,
costTime,
objectMapper.writeValueAsString(result)
);
return result;
} catch (Exception e) {
long costTime = System.currentTimeMillis() - startTime;
log.error("Request [{}]异常: 耗时={}ms, 错误={}",
requestId, costTime, e.getMessage(), e);
throw e;
}
}
}
八、接口性能优化规范
package com.example.optimization;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@RestController
public class OptimizedApiController {
/**
* 1. 使用缓存
*/
@GetMapping("/api/cached-data")
public ResponseEntity<Data> getCachedData(@RequestParam String key) {
// 先从缓存获取
Data cached = redisTemplate.opsForValue().get("data:" + key);
if (cached != null) {
return ResponseEntity.ok(cached);
}
// 缓存不存在,从数据库查询
Data data = dataService.queryFromDB(key);
// 存入缓存(5分钟过期)
redisTemplate.opsForValue().set("data:" + key, data, 5, TimeUnit.MINUTES);
return ResponseEntity.ok(data);
}
/**
* 2. 异步处理
*/
@GetMapping("/api/async-process")
@Async
public CompletableFuture<ResponseEntity<ProcessResult>> asyncProcess() {
// 异步处理耗时操作
ProcessResult result = heavyProcess();
return CompletableFuture.completedFuture(ResponseEntity.ok(result));
}
/**
* 3. 批量操作
*/
@PostMapping("/api/batch-users")
public ResponseEntity<List<UserResponse>> batchGetUsers(
@RequestBody List<String> userIds) {
// 批量查询,避免循环单个查询
List<UserResponse> users = userService.batchGetByIds(userIds);
return ResponseEntity.ok(users);
}
/**
* 4. 压缩响应
*/
@GetMapping(value = "/api/large-data", produces = "application/json")
public ResponseEntity<LargeData> getLargeData() {
LargeData data = dataService.getLargeData();
return ResponseEntity.ok()
.header("Content-Encoding", "gzip")
.body(data);
}
}
九、接口测试规范
package com.example.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.hamcrest.Matchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
/**
* 测试创建用户
*/
@Test
public void testCreateUser() throws Exception {
String requestJson = """
{
"username": "testuser",
"password": "password123",
"email": "test@example.com",
"phone": "13800138000",
"age": 25
}
""";
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username", is("testuser")))
.andExpect(jsonPath("$.email", is("test@example.com")))
.andExpect(jsonPath("$.id", notNullValue()));
}
/**
* 测试参数校验
*/
@Test
public void testCreateUser_Validation() throws Exception {
String requestJson = """
{
"username": "te", // 太短
"password": "123", // 太短
"email": "not-email" // 格式错误
}
""";
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.details.username", notNullValue()))
.andExpect(jsonPath("$.details.password", notNullValue()))
.andExpect(jsonPath("$.details.email", notNullValue()));
}
}
十、接口设计原则总结
资源导向:使用名词而不是动词(/users 而不是 /getUsers)
HTTP方法语义:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)
状态码使用:200(成功)、201(创建成功)、400(参数错误)、404(资源不存在)
URL:小写字母,单词间用连字符(/user-profiles)
字段:驼峰命名(userId, createdAt)
枚举:大写字母,下划线分隔(ACTIVE, PENDING_APPROVAL)
鉴权:所有接口都需要鉴权(除登录注册外)
防篡改:敏感操作需要签名验证
防重放:添加时间戳和nonce
限流:防止恶意调用
URL路径:/v1/users, /v2/users
请求头:Accept: application/vnd.company.v1+json
向后兼容:新增字段不影响旧版本