偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

看看人家,那后端API接口寫得,那叫一個(gè)優(yōu)雅!

開發(fā) 前端
寫優(yōu)雅的 API 就像做一道好菜,食材(技術(shù))重要,火候(經(jīng)驗(yàn))重要,用心(態(tài)度)更重要。一個(gè)優(yōu)雅的 API 能體現(xiàn)開發(fā)者的專業(yè)素養(yǎng),也能讓整個(gè)團(tuán)隊(duì)的協(xié)作效率事半功倍。

兄弟們,作為一名 Java 后端開發(fā)者,我敢打賭你一定遇到過(guò)這種情況:對(duì)接別人的 API 時(shí)順風(fēng)順?biāo)?,代碼寫得行云流水,下班前就能輕松搞定;而對(duì)接某些 API 時(shí),文檔看得一頭霧水,參數(shù)傳得懷疑人生,調(diào)試到深夜還在群里卑微提問(wèn):"大佬,這個(gè)接口到底要傳啥啊?"

這就是優(yōu)雅與粗糙的 API 之間的天壤之別。一個(gè)優(yōu)雅的 API 就像一位貼心的服務(wù)員,你剛抬手就知道要加水;而一個(gè)糟糕的 API 就像走進(jìn)了迷宮餐廳,找個(gè)座位都得求助三次。今天咱們就來(lái)好好聊聊,如何把自己的后端 API 接口打造成 "五星級(jí)服務(wù)水準(zhǔn)",讓同事和調(diào)用者都忍不住點(diǎn)贊。

一、命名規(guī)范:給你的 API 起個(gè) "好記的名字"

首先咱們得明確一點(diǎn):API 是給人用的,不是給機(jī)器看的。機(jī)器只認(rèn) 0 和 1,但人需要清晰的語(yǔ)義。想象一下,你去飯店吃飯,菜單上寫著 "固體碳水化合物配高溫油炸蛋白質(zhì)",你知道這是啥嗎?其實(shí)就是 "炸薯?xiàng)l配炸雞" 啊!

資源命名的黃金法則

優(yōu)雅的 API 命名首先要遵循 "資源導(dǎo)向" 原則。什么意思?就是用名詞而不是動(dòng)詞來(lái)表示 API 操作的資源。比如:

// 反面教材:用動(dòng)詞做資源名
@GetMapping("/getUserById")
@GetMapping("/deleteUser")
@GetMapping("/updateUserInfo")
// 優(yōu)雅示范:用名詞表示資源
@GetMapping("/users/{id}")    // 獲取用戶
@DeleteMapping("/users/{id}") // 刪除用戶
@PutMapping("/users/{id}")    // 更新用戶

看到?jīng)]?后者就像菜單上的 "宮保雞丁",一看就知道是啥;前者則像 "廚師現(xiàn)做雞肉花生米炒辣椒",啰嗦又難懂。RESTful 風(fēng)格之所以流行,就是因?yàn)樗?HTTP 方法(GET/POST/PUT/DELETE)來(lái)表示操作,用 URL 來(lái)表示資源,天然符合人類的理解習(xí)慣。

復(fù)數(shù)還是單數(shù)?這是個(gè)問(wèn)題

關(guān)于資源名用復(fù)數(shù)還是單數(shù),業(yè)界一直有爭(zhēng)論。我的建議是:用復(fù)數(shù)。因?yàn)?API 操作的通常是資源集合中的一個(gè)或多個(gè)元素,比如/users表示用戶集合,/users/123表示集合中的某個(gè)用戶。就像你去水果店買蘋果,老板會(huì)問(wèn)你 "要幾個(gè)蘋果" 而不是 "要幾個(gè)蘋果項(xiàng)"。

// 推薦寫法
@GetMapping("/users")       // 獲取用戶列表
@GetMapping("/users/{id}")  // 獲取單個(gè)用戶
// 不推薦寫法
@GetMapping("/user")        // 單個(gè)用戶?還是所有用戶?
@GetMapping("/userList")    // 冗余,復(fù)數(shù)已經(jīng)表示列表

版本控制要直白

API 迭代是難免的,這時(shí)候版本控制就很重要了。我見(jiàn)過(guò)最離譜的版本控制是用日期做版本號(hào),比如/api/20250818/users,每次更新都得改日期,調(diào)用者苦不堪言。

優(yōu)雅的做法是在 URL 中明確標(biāo)注主版本號(hào):

// 推薦方式
@GetMapping("/api/v1/users")  // v1表示第一版API
@GetMapping("/api/v2/users")  // v2表示不兼容的升級(jí)版
// 不推薦方式
@GetMapping("/api/users?version=1")  // 版本藏在參數(shù)里
@GetMapping("/newApi/users")         // 用新老區(qū)分,后續(xù)版本怎么辦?

這樣做的好處是一目了然,調(diào)用者可以清晰知道自己在用哪個(gè)版本的 API,升級(jí)時(shí)也能有明確的遷移目標(biāo)。

二、統(tǒng)一響應(yīng):給調(diào)用者一個(gè) "定心丸"

想象一下這種場(chǎng)景:調(diào)用 API 時(shí),成功返回的是{code:200, data:{...}},失敗返回的是{status:false, message:"出錯(cuò)了"},偶爾還會(huì)直接返回異常堆棧信息。這種 "薛定諤的響應(yīng)格式" 會(huì)讓前端同學(xué)崩潰的!

優(yōu)雅的 API 必須有統(tǒng)一的響應(yīng)格式,就像飯店不管上什么菜,都會(huì)用符合餐廳風(fēng)格的盤子裝一樣。

響應(yīng)體的標(biāo)準(zhǔn)結(jié)構(gòu)

一個(gè)標(biāo)準(zhǔn)的 API 響應(yīng)體應(yīng)該包含這些要素:

  • 狀態(tài)碼(code):業(yè)務(wù)層面的狀態(tài)標(biāo)識(shí)
  • 消息(message):操作結(jié)果的文字描述
  • 數(shù)據(jù)(data):實(shí)際返回的業(yè)務(wù)數(shù)據(jù)
  • 時(shí)間戳(timestamp):響應(yīng)時(shí)間,方便排查問(wèn)題

我們可以定義一個(gè)通用的響應(yīng) DTO:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    // 業(yè)務(wù)狀態(tài)碼,200表示成功
    private int code;
    // 響應(yīng)消息
    private String message;
    // 響應(yīng)數(shù)據(jù)
    private T data;
    // 響應(yīng)時(shí)間戳
    private long timestamp;
    // 成功響應(yīng)的靜態(tài)方法
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "操作成功", data, System.currentTimeMillis());
    }
    // 錯(cuò)誤響應(yīng)的靜態(tài)方法
    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null, System.currentTimeMillis());
    }
}

使用這種統(tǒng)一響應(yīng)后,所有接口的返回格式保持一致:

@GetMapping("/users/{id}")
public ApiResponse<UserDTO> getUser(@PathVariable Long id) {
    UserDTO user = userService.findById(id);
    return ApiResponse.success(user);
}
// 響應(yīng)結(jié)果:
{
  "code": 200,
  "message": "操作成功",
  "data": {
    "id": 123,
    "name": "張三"
  },
  "timestamp": 1629260852341
}

異常處理:不能讓調(diào)用者猜謎

當(dāng) API 發(fā)生錯(cuò)誤時(shí),最忌諱的就是返回模棱兩可的錯(cuò)誤信息。優(yōu)雅的 API 應(yīng)該像醫(yī)生看病一樣,清晰告訴調(diào)用者 "哪里出了問(wèn)題"、"怎么解決"。

在 Spring Boot 中,我們可以用全局異常處理器來(lái)統(tǒng)一處理所有異常:

@RestControllerAdvice
public class GlobalExceptionHandler {
    // 處理業(yè)務(wù)異常
    @ExceptionHandler(ApplicationException.class)
    public ApiResponse<Void> handleApplicationException(ApplicationException e) {
        log.error("業(yè)務(wù)異常: {}", e.getMessage());
        return ApiResponse.error(e.getCode(), e.getMessage());
    }
    // 處理參數(shù)校驗(yàn)異常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
        // 獲取校驗(yàn)失敗的字段和消息
        String message = e.getBindingResult().getFieldErrors().stream()
                .map(error -> error.getField() + ":" + error.getDefaultMessage())
                .collect(Collectors.joining("; "));
        log.error("參數(shù)校驗(yàn)異常: {}", message);
        return ApiResponse.error(400, "參數(shù)錯(cuò)誤: " + message);
    }
    // 處理未知異常
    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleUnknownException(Exception e) {
        log.error("未知異常", e); // 記錄完整堆棧
        // 給用戶返回友好信息,不暴露敏感內(nèi)容
        return ApiResponse.error(500, "服務(wù)器內(nèi)部錯(cuò)誤,請(qǐng)聯(lián)系管理員");
    }
}

然后定義業(yè)務(wù)異常類,讓異常能夠 "自我描述":

@Data
@AllArgsConstructor
public class ApplicationException extends RuntimeException {
    private int code;
    private String message;
    // 靜態(tài)方法簡(jiǎn)化異常創(chuàng)建
    public static ApplicationException of(int code, String message) {
        return new ApplicationException(code, message);
    }
    // 常用異常枚舉
    public static ApplicationException resourceNotFound() {
        return new ApplicationException(404, "資源不存在");
    }
    
    public static ApplicationException permissionDenied() {
        return new ApplicationException(403, "沒(méi)有操作權(quán)限");
    }
}

這樣處理后,當(dāng)業(yè)務(wù)發(fā)生異常時(shí),我們只需在代碼中拋出異常:

public UserDTO findById(Long id) {
    User user = userMapper.selectById(id);
    if (user == null) {
        throw ApplicationException.resourceNotFound();
    }
    return convertToDTO(user);
}

調(diào)用者會(huì)收到清晰的錯(cuò)誤提示,而不是一堆晦澀的異常堆棧。這種方式既保證了代碼的整潔(不用在 Controller 層寫大量 try-catch),又能給調(diào)用者明確的反饋,可謂一舉兩得。

三、參數(shù)校驗(yàn):把問(wèn)題擋在門外

你有沒(méi)有遇到過(guò)這種情況:接口實(shí)現(xiàn)里充滿了各種參數(shù)校驗(yàn)邏輯,一半代碼都在做 "如果參數(shù) A 為空怎么辦"、"如果參數(shù) B 格式不對(duì)怎么辦"。這種代碼不僅臃腫,而且容易遺漏校驗(yàn)邏輯。

優(yōu)雅的 API 應(yīng)該在入口處就做好參數(shù)校驗(yàn),就像火車站的安檢一樣,危險(xiǎn)品根本進(jìn)不了站。

JSR-303 校驗(yàn)的正確姿勢(shì)

Spring Boot 支持 JSR-303 規(guī)范的參數(shù)校驗(yàn),通過(guò)注解就能輕松實(shí)現(xiàn)參數(shù)校驗(yàn):

@Data
public class UserCreateDTO {
    @NotBlank(message = "用戶名不能為空")
    @Size(min = 2, max = 20, message = "用戶名長(zhǎng)度必須在2-20之間")
    private String username;

    @NotBlank(message = "密碼不能為空")
    @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&*])[a-zA-Z0-9@#$%^&*]{8,20}$", 
             message = "密碼必須包含字母、數(shù)字和特殊字符,長(zhǎng)度8-20")
    private String password;

    @NotNull(message = "年齡不能為空")
    @Min(value = 0, message = "年齡不能小于0")
    @Max(value = 150, message = "年齡不能大于150")
    private Integer age;

    @Email(message = "郵箱格式不正確")
    private String email;
}

在 Controller 中只需添加@Valid注解啟用校驗(yàn):

@PostMapping("/users")
public ApiResponse<UserDTO> createUser(@Valid @RequestBody UserCreateDTO userDTO) {
    // 校驗(yàn)通過(guò)才會(huì)執(zhí)行到這里
    UserDTO created = userService.create(userDTO);
    return ApiResponse.success(created);
}

當(dāng)參數(shù)不符合要求時(shí),會(huì)自動(dòng)拋出MethodArgumentNotValidException,然后被我們前面定義的全局異常處理器捕獲,返回統(tǒng)一的錯(cuò)誤響應(yīng):

{
  "code": 400,
  "message": "參數(shù)錯(cuò)誤: username:用戶名不能為空; password:密碼格式不正確",
  "data": null,
  "timestamp": 1629261523456
}

這種方式把校驗(yàn)邏輯和業(yè)務(wù)邏輯完美分離,代碼瞬間清爽了不少。

分組校驗(yàn):應(yīng)對(duì)復(fù)雜場(chǎng)景

有時(shí)候同一個(gè) DTO 在不同場(chǎng)景下有不同的校驗(yàn)規(guī)則。比如創(chuàng)建用戶時(shí)不需要傳 ID(自動(dòng)生成),但更新用戶時(shí)必須傳 ID。這時(shí)候可以用分組校驗(yàn)來(lái)解決:

// 定義分組接口
publicinterfaceCreateGroup {}
publicinterfaceUpdateGroup {}

@Data
public class UserDTO {
    // 更新時(shí)必須傳ID,創(chuàng)建時(shí)不需要
    @NotNull(message = "ID不能為空", groups = UpdateGroup.class)
    private Long id;

    @NotBlank(message = "用戶名不能為空", groups = {CreateGroup.class, UpdateGroup.class})
    private String username;

    // 創(chuàng)建時(shí)必須傳密碼,更新時(shí)可以不傳
    @NotBlank(message = "密碼不能為空", groups = CreateGroup.class)
    private String password;
}

// 在Controller中指定分組
@PostMapping("/users")
public ApiResponse<UserDTO> createUser(@Validated(CreateGroup.class) @RequestBody UserDTO userDTO) {
    // 創(chuàng)建用戶邏輯
}

@PutMapping("/users")
public ApiResponse<UserDTO> updateUser(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
    // 更新用戶邏輯
}

通過(guò)分組校驗(yàn),我們可以用一個(gè) DTO 應(yīng)對(duì)不同場(chǎng)景,避免創(chuàng)建大量類似的 DTO 類。

四、文檔自動(dòng)生成:別讓文檔成為負(fù)擔(dān)

"寫 API 文檔" 大概是很多開發(fā)者最頭疼的事情之一。要么是寫完代碼忘了更新文檔,導(dǎo)致文檔和實(shí)際接口不一致;要么是文檔寫得太簡(jiǎn)略,調(diào)用者看得云里霧里。

優(yōu)雅的 API 應(yīng)該有清晰的文檔,而且最好是自動(dòng)生成的,免得 "寫文檔" 成為額外負(fù)擔(dān)。

OpenAPI 3.1 + SpringDoc:文檔自動(dòng)生成方案

OpenAPI 3.1(原 Swagger)是目前最流行的 API 文檔規(guī)范,它可以根據(jù)代碼自動(dòng)生成交互式 API 文檔。SpringDoc 是 Spring Boot 的 OpenAPI 實(shí)現(xiàn),用法非常簡(jiǎn)單。

首先添加依賴:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.1.0</version>
</dependency>

然后添加配置:

@Configuration
@OpenAPIDefinition(
    info = @Info(
        title = "用戶管理API",
        version = "v1",
        description = "用戶CRUD及相關(guān)操作的API接口文檔",
        contact = @Contact(name = "技術(shù)支持", email = "support@example.com")
    ),
    servers = {
        @Server(url = "http://localhost:8080", description = "開發(fā)環(huán)境"),
        @Server(url = "http://api.example.com", description = "生產(chǎn)環(huán)境")
    }
)
public class OpenApiConfig {
    // 可以配置安全方案,如JWT認(rèn)證
    @Bean
    public SecurityScheme securityScheme() {
        returnSecurityScheme.builder()
                .type(SecurityScheme.Type.HTTP)
                .scheme("bearer")
                .bearerFormat("JWT")
                .name("Authorization")
                .build();
    }
}

接下來(lái)在接口和 DTO 中添加注釋:

@Operation(summary = "創(chuàng)建用戶", description = "新增用戶信息,返回創(chuàng)建成功的用戶詳情")
@ApiResponses({
    @ApiResponse(responseCode = "200", description = "創(chuàng)建成功"),
    @ApiResponse(responseCode = "400", description = "參數(shù)錯(cuò)誤")
})
@PostMapping("/users")
public ApiResponse<UserDTO> createUser(
    @Parameter(description = "用戶信息", required = true)
    @Valid @RequestBody UserCreateDTO userDTO) {
    // 業(yè)務(wù)邏輯
}

啟動(dòng)項(xiàng)目后訪問(wèn)http://localhost:8080/swagger-ui.html,就能看到交互式的 API 文檔了。調(diào)用者可以直接在頁(yè)面上測(cè)試接口,查看參數(shù)說(shuō)明,再也不用對(duì)著文檔猜參數(shù)了。OpenAPI 3.1 相比之前的版本最大的改進(jìn)是完全兼容 JSON Schema,這意味著你的 JSON 數(shù)據(jù)結(jié)構(gòu)定義可以在 API 文檔和代碼校驗(yàn)中共享,避免重復(fù)定義。

五、安全設(shè)計(jì):API 也需要 "防盜門"

優(yōu)雅的 API 不僅要好用,更要安全。想象一下,你辛辛苦苦設(shè)計(jì)的 API,如果沒(méi)有安全防護(hù),就像家里沒(méi)裝防盜門,很容易被 "不速之客" 光顧。

Token 認(rèn)證:給 API 加把鎖

最常見(jiàn)的 API 安全方案是 Token 認(rèn)證,流程如下:

  1. 客戶端使用用戶名密碼換取 Token
  2. 客戶端每次請(qǐng)求 API 時(shí)在 Header 中攜帶 Token
  3. 服務(wù)器驗(yàn)證 Token 的有效性,通過(guò)則允許訪問(wèn)

實(shí)現(xiàn) JWT(JSON Web Token)認(rèn)證的代碼示例:

@RestController
@RequestMapping("/auth")
publicclass AuthController {
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Autowired
    private UserDetailsService userDetailsService;

    @PostMapping("/login")
    public ApiResponse<String> login(@RequestBody LoginRequest request) {
        // 驗(yàn)證用戶名密碼
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
        );
        
        // 生成Token
        String token = tokenProvider.generateToken(authentication);
        return ApiResponse.success(token);
    }
}

// JWT工具類
@Component
publicclass JwtTokenProvider {
    @Value("${app.jwt.secret}")
    privateString jwtSecret;
    
    @Value("${app.jwt.expiration}")
    private long jwtExpirationMs;

    // 生成Token
    publicString generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = newDate();
        Date expiryDate = newDate(now.getTime() + jwtExpirationMs);
        
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }
    
    // 驗(yàn)證Token
    publicboolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            returntrue;
        } catch (Exception e) {
            // Token無(wú)效或過(guò)期
            returnfalse;
        }
    }
}

// 認(rèn)證過(guò)濾器
@Component
publicclass JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protectedvoid doFilterInternal(HttpServletRequest request, 
                                   HttpServletResponse response, 
                                   FilterChain filterChain) {
        // 從Header中獲取Token
        String token = extractTokenFromHeader(request);
        
        // 驗(yàn)證Token
        if (token != null && tokenProvider.validateToken(token)) {
            // 設(shè)置認(rèn)證信息
            Authentication auth = getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        
        filterChain.doFilter(request, response);
    }
}

接口限流:防止 "暴飲暴食"

即使加了認(rèn)證,我們的 API 還需要防止被頻繁調(diào)用導(dǎo)致服務(wù)崩潰。就像飯店會(huì)限制同一桌的點(diǎn)餐數(shù)量,API 也需要限流保護(hù)。

使用 Spring Cloud Gateway 或 Sentinel 可以實(shí)現(xiàn)限流,簡(jiǎn)單場(chǎng)景下也可以用 Redis 自己實(shí)現(xiàn):

@Component
publicclass RateLimiter {
    @Autowired
    private StringRedisTemplate redisTemplate;

    // 嘗試獲取令牌,返回true表示允許訪問(wèn)
    public boolean tryAcquire(String key, int maxRequests, int periodSeconds) {
        String redisKey = "rate_limit:" + key;
        // 使用Redis的INCR命令自增
        Long count = redisTemplate.opsForValue().increment(redisKey);
        
        // 第一次訪問(wèn),設(shè)置過(guò)期時(shí)間
        if (count != null && count == 1) {
            redisTemplate.expire(redisKey, periodSeconds, TimeUnit.SECONDS);
        }
        
        // 判斷是否超過(guò)限制
        return count != null && count <= maxRequests;
    }
}

// 在Controller中使用
@GetMapping("/users")
public ApiResponse<List<UserDTO>> getUsers() {
    String clientIp = getClientIp(request);
    // 限制同一IP每分鐘最多訪問(wèn)60次
    if (!rateLimiter.tryAcquire(clientIp, 60, 60)) {
        throw ApplicationException.of(429, "請(qǐng)求過(guò)于頻繁,請(qǐng)稍后再試");
    }
    // 業(yè)務(wù)邏輯
}

敏感數(shù)據(jù)脫敏:保護(hù)用戶隱私

API 返回的響應(yīng)中可能包含敏感信息,如手機(jī)號(hào)、郵箱、身份證號(hào)等。優(yōu)雅的 API 應(yīng)該對(duì)這些信息進(jìn)行脫敏處理:

public class SensitiveDataUtils {
    // 手機(jī)號(hào)脫敏:138****5678
    publicstaticString maskPhone(String phone) {
        if (phone == null || phone.length() != 11) {
            return phone;
        }
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
    
    // 郵箱脫敏:z***@example.com
    publicstaticString maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        String[] parts = email.split("@");
        if (parts[0].length() <= 1) {
            return parts[0] + "***@" + parts[1];
        }
        return parts[0].substring(0, 1) + "***@" + parts[1];
    }
}

// 在DTO中使用
@Data
publicclass UserDTO {
    private Long id;
    privateString username;
    
    // 序列化時(shí)自動(dòng)脫敏
    @JsonSerialize(using = SensitivePhoneSerializer.class)
    privateString phone;
    
    @JsonSerialize(using = SensitiveEmailSerializer.class)
    privateString email;
}

六、性能優(yōu)化:讓 API 跑起來(lái)

優(yōu)雅的 API 不僅要好用、安全,還得跑得快。沒(méi)人愿意用一個(gè)響應(yīng)慢吞吞的 API,就像沒(méi)人愿意等上半小時(shí)才上菜的餐廳。

緩存策略:減少重復(fù)勞動(dòng)

對(duì)于頻繁訪問(wèn)且不常變化的數(shù)據(jù),緩存是提升性能的利器。我們可以用 Spring 的緩存抽象來(lái)實(shí)現(xiàn):

@Configuration
@EnableCaching
publicclass CacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 配置Redis緩存
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10)) // 默認(rèn)過(guò)期時(shí)間
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        // 針對(duì)不同緩存設(shè)置不同過(guò)期時(shí)間
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        cacheConfigurations.put("users", config.entryTtl(Duration.ofHours(1)));
        cacheConfigurations.put("roles", config.entryTtl(Duration.ofHours(24)));
        
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .withInitialCacheConfigurations(cacheConfigurations)
                .build();
    }
}

// 在Service中使用緩存
@Service
publicclass UserService {
    // 查詢時(shí)先查緩存,沒(méi)有再查數(shù)據(jù)庫(kù)
    @Cacheable(value = "users", key = "#id")
    public UserDTO findById(Long id) {
        // 數(shù)據(jù)庫(kù)查詢邏輯
    }
    
    // 更新時(shí)同步更新緩存
    @CachePut(value = "users", key = "#userDTO.id")
    public UserDTO update(UserDTO userDTO) {
        // 更新數(shù)據(jù)庫(kù)邏輯
    }
    
    // 刪除時(shí)清除緩存
    @CacheEvict(value = "users", key = "#id")
    public void delete(Long id) {
        // 刪除數(shù)據(jù)庫(kù)記錄邏輯
    }
}

對(duì)于更復(fù)雜的場(chǎng)景,比如熱點(diǎn)數(shù)據(jù)緩存、緩存預(yù)熱,我們可以實(shí)現(xiàn)自定義緩存管理器,在系統(tǒng)啟動(dòng)時(shí)預(yù)加載常用數(shù)據(jù)到緩存中。

異步處理:不阻塞主線程

對(duì)于耗時(shí)操作(如發(fā)送郵件、生成報(bào)表),我們不應(yīng)該讓 API 調(diào)用者一直等待,可以用異步處理來(lái)提升響應(yīng)速度:

@Configuration
@EnableAsync
publicclass AsyncConfig {
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 核心線程數(shù)
        executor.setMaxPoolSize(10); // 最大線程數(shù)
        executor.setQueueCapacity(25); // 隊(duì)列容量
        executor.setThreadNamePrefix("async-"); // 線程名前綴
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

@Service
publicclass NotificationService {
    // 標(biāo)記為異步方法
    @Async("taskExecutor")
    public CompletableFuture<Void> sendWelcomeEmail(String email) {
        // 發(fā)送郵件的耗時(shí)操作
        return CompletableFuture.runAsync(() -> {
            // 實(shí)際發(fā)送邏輯
            try {
                Thread.sleep(1000); // 模擬耗時(shí)操作
                log.info("發(fā)送歡迎郵件到: {}", email);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
}

// 在Controller中使用
@PostMapping("/users")
public ApiResponse<UserDTO> createUser(@Valid @RequestBody UserCreateDTO userDTO) {
    UserDTO created = userService.create(userDTO);
    // 異步發(fā)送郵件,不阻塞當(dāng)前請(qǐng)求
    notificationService.sendWelcomeEmail(created.getEmail());
    return ApiResponse.success(created);
}

這樣處理后,用戶注冊(cè)接口的響應(yīng)時(shí)間不會(huì)受到發(fā)送郵件耗時(shí)的影響,大大提升了用戶體驗(yàn)。

七、實(shí)戰(zhàn)經(jīng)驗(yàn):優(yōu)雅不是炫技

最后我想分享一些實(shí)戰(zhàn)經(jīng)驗(yàn)。優(yōu)雅的 API 不是炫技,而是解決實(shí)際問(wèn)題的智慧。過(guò)度設(shè)計(jì)的 API 就像裝修過(guò)度的房子,華而不實(shí),維護(hù)成本還高。

保持簡(jiǎn)單

能用 GET 請(qǐng)求解決的問(wèn)題,就不要用 POST;能少傳一個(gè)參數(shù),就不要多傳。記住,每個(gè)額外的參數(shù)都是給調(diào)用者的負(fù)擔(dān)。

向后兼容

API 升級(jí)時(shí)一定要保持向后兼容。不要輕易刪除字段或改變參數(shù)含義,可以新增字段或新增版本,給調(diào)用者足夠的遷移時(shí)間。

監(jiān)控與日志

優(yōu)雅的 API 需要完善的監(jiān)控和日志。通過(guò) AOP 記錄接口的調(diào)用情況、響應(yīng)時(shí)間、異常信息,這樣出現(xiàn)問(wèn)題時(shí)才能快速定位:

@Aspect
@Component
@Slf4j
publicclass ApiMonitorAspect {
    @Around("execution(* com.example.api.controller..*(..)) && @annotation(requestMapping)")
    public Object monitorApi(ProceedingJoinPoint joinPoint, RequestMapping requestMapping) throws Throwable {
        long start = System.currentTimeMillis();
        String method = Arrays.stream(requestMapping.method())
                .map(HttpMethod::name)
                .findFirst()
                .orElse("UNKNOWN");
        String path = Arrays.stream(requestMapping.value()).findFirst().orElse("");
        
        try {
            Object result = joinPoint.proceed();
            long cost = System.currentTimeMillis() - start;
            log.info("API調(diào)用: {} {} 耗時(shí): {}ms", method, path, cost);
            return result;
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - start;
            log.error("API異常: {} {} 耗時(shí): {}ms", method, path, cost, e);
            throw e;
        }
    }
}

結(jié)語(yǔ)

寫優(yōu)雅的 API 就像做一道好菜,食材(技術(shù))重要,火候(經(jīng)驗(yàn))重要,用心(態(tài)度)更重要。一個(gè)優(yōu)雅的 API 能體現(xiàn)開發(fā)者的專業(yè)素養(yǎng),也能讓整個(gè)團(tuán)隊(duì)的協(xié)作效率事半功倍。

希望這篇文章能給你帶來(lái)啟發(fā)。下次寫 API 的時(shí)候,不妨多站在調(diào)用者的角度想一想:這個(gè)接口好懂嗎?好用嗎?安全嗎?高效嗎?

責(zé)任編輯:武曉燕 來(lái)源: 石杉的架構(gòu)筆記
相關(guān)推薦

2020-11-03 16:00:33

API接口微服務(wù)框架編程語(yǔ)言

2020-11-17 09:34:31

API接口后端

2022-12-12 08:14:47

2025-07-14 00:00:00

接口重試MQTT冪等性

2025-03-11 08:20:58

2025-05-30 08:20:54

2024-11-12 08:20:31

2025-04-08 08:20:33

2024-10-24 08:21:33

2025-03-06 08:21:02

判空entity對(duì)象

2024-12-02 00:59:30

Spring

2025-04-22 08:20:51

2025-02-28 08:21:00

2023-12-30 20:04:51

MyBatis框架數(shù)據(jù)

2024-10-17 09:21:30

2019-12-04 09:54:03

深度學(xué)習(xí)編程人工智能

2025-04-02 12:20:00

開發(fā)代碼函數(shù)

2024-11-08 15:56:36

2022-06-21 14:44:38

接口數(shù)據(jù)脫敏

2024-11-07 10:55:26

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)