Spring Boot 接口響應封裝與國際化、多租戶拓展實戰
在企業級應用中,構建一個統一、標準、可擴展的接口返回結構,對于前后端協作、異常處理、國際化、多租戶支持等都有重要意義。本文基于 Spring Boot 框架,提供一套完整的接口封裝方案,并進一步集成:
- 通用返回值結構 Result<T>
- 分頁數據結構 PageResult<T>
- 國際化消息支持(i18n)
- 多語言錯誤碼體系
- 多租戶 traceId 注入機制
- 全局異常統一處理
- 自動封裝響應體
- Swagger 文檔兼容
項目結構總覽
src/
└── main/
├── java/
│ └── com/icoderoad/
│ ├── common/
│ │ ├── model/ # Result、PageResult、ErrorCode
│ │ ├── handler/ # ResponseBodyAdvice & ExceptionHandler
│ │ ├── i18n/ # 國際化消息工具
│ │ └── trace/ # traceId / tenantId 線程上下文
│ └── user/controller/ # 示例業務接口
└── resources/
├── messages.properties
├── messages_en.properties
└── application.yml
統一返回結構:Result
/common/model/Result.java
package com.icoderoad.common.model;
public class Result<T> {
private int code;
private String msg;
private T data;
private String traceId;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ErrorCode.SUCCESS.getCode());
result.setMsg(ErrorCode.SUCCESS.getMessage());
result.setData(data);
result.setTraceId(TraceContextHolder.getTraceId());
return result;
}
public static <T> Result<T> failed(ErrorCode errorCode) {
Result<T> result = new Result<>();
result.setCode(errorCode.getCode());
result.setMsg(errorCode.getMessage());
result.setTraceId(TraceContextHolder.getTraceId());
return result;
}
public Result<T> withMsg(String msg) {
this.msg = msg;
return this;
}
public Result<T> withCode(int code) {
this.code = code;
return this;
}
public Result<T> withTraceId(String traceId) {
this.traceId = traceId;
return this;
}
// Getter & Setter 省略
}
分頁響應結構:PageResult
/common/model/PageResult.java
package com.icoderoad.common.model;
import java.util.List;
public class PageResult<T> {
private long pageNum;
private long pageSize;
private long total;
private long totalPage;
private List<T> list;
public PageResult(long pageNum, long pageSize, long total, List<T> list) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.total = total;
this.list = list;
this.totalPage = (long) Math.ceil((double) total / pageSize);
}
// Getter & Setter 省略
}
統一錯誤碼定義:ErrorCode 枚舉
/common/model/ErrorCode.java
package com.icoderoad.common.model;
import com.icoderoad.common.i18n.MessageUtils;
public enum ErrorCode {
SUCCESS(200, "success"),
FAILED(500, "failed"),
UNAUTHORIZED(401, "unauthorized"),
USER_NOT_FOUND(40001, "user.not.found"),
SYSTEM_ERROR(50001, "system.error");
private final int code;
private final String i18nKey;
ErrorCode(int code, String i18nKey) {
this.code = code;
this.i18nKey = i18nKey;
}
public int getCode() {
return code;
}
public String getMessage() {
return MessageUtils.get(i18nKey);
}
}
國際化支持:MessageUtils 工具類
/common/i18n/MessageUtils.java
package com.icoderoad.common.i18n;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Locale;
@Component
public class MessageUtils {
private static MessageSource messageSource;
@Autowired
public MessageUtils(MessageSource messageSource) {
MessageUtils.messageSource = messageSource;
}
public static String get(String key, Object... args) {
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(key, args, locale);
}
}
/resources/messages.properties
success=成功
failed=失敗
unauthorized=未授權
user.not.found=用戶不存在
system.error=系統異常
/common/trace/TraceContextHolder.java
package com.icoderoad.common.trace;
public class TraceContextHolder {
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>();
public static void setTraceId(String traceId) {
TRACE_ID.set(traceId);
}
public static String getTraceId() {
return TRACE_ID.get();
}
public static void setTenantId(String tenantId) {
TENANT_ID.set(tenantId);
}
public static String getTenantId() {
return TENANT_ID.get();
}
public static void clear() {
TRACE_ID.remove();
TENANT_ID.remove();
}
}
/common/trace/TraceInterceptor.java
package com.icoderoad.common.trace;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@Component
public class TraceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = UUID.randomUUID().toString().replaceAll("-", "");
String tenantId = request.getHeader("X-Tenant-ID");
TraceContextHolder.setTraceId(traceId);
TraceContextHolder.setTenantId(tenantId != null ? tenantId : "default");
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
TraceContextHolder.clear();
}
}
自動封裝返回值:ResponseBodyAdvice
/common/handler/GlobalResponseHandler.java
package com.icoderoad.common.handler;
import com.alibaba.fastjson.JSON;
import com.icoderoad.common.model.Result;
import com.icoderoad.common.trace.TraceContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return !Result.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
return JSON.toJSONString(Result.success(body));
}
return Result.success(body);
}
}
統一異常處理
/common/handler/GlobalExceptionHandler.java
package com.icoderoad.common.handler;
import com.icoderoad.common.model.ErrorCode;
import com.icoderoad.common.model.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<String> handle(Exception e) {
if (e instanceof BusinessException) {
return Result.failed(ErrorCode.USER_NOT_FOUND).withMsg(e.getMessage());
} else if (e instanceof UnauthorizedException) {
return Result.failed(ErrorCode.UNAUTHORIZED);
}
return Result.failed(ErrorCode.SYSTEM_ERROR);
}
}
Swagger 集成通用返回類型
/common/config/SwaggerConfig.java
package com.icoderoad.common.config;
import com.icoderoad.common.model.Result;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SwaggerConfig {
@Bean
public Docket createApi() {
return new Docket(DocumentationType.SWAGGER_2)
.genericModelSubstitutes(Result.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.icoderoad"))
.paths(PathSelectors.any())
.build();
}
}
使用示例:分頁用戶查詢
@GetMapping("/users")
public Result<PageResult<User>> getUsers(@RequestParam int pageNum, @RequestParam int pageSize) {
PageInfo<User> pageInfo = userService.getUsers(pageNum, pageSize);
PageResult<User> pageResult = new PageResult<>(
pageInfo.getPageNum(), pageInfo.getPageSize(), pageInfo.getTotal(), pageInfo.getList());
return Result.success(pageResult);
}
總結
特性 | 實現方案 |
通用響應結構 | Result / PageResult |
異常統一封裝 | GlobalExceptionHandler |
自動響應包裝 | ResponseBodyAdvice |
國際化支持 | MessageSource + messages.properties |
錯誤碼多語言 | ErrorCode + i18nKey |
traceId + 多租戶 | 攔截器 + ThreadLocal |
API文檔兼容 | Swagger genericModelSubstitutes |