Spring Boot接口国际化异常信息方案

要实现Java后端接口根据请求头的语言字段返回对应语言的异常信息,核心思路是国际化配置 + 全局异常处理 + 请求头语言解析。以下是基于Spring Boot的完整实现方案:

一、整体方案设计

  1. 语言标识约定 :请求头中自定义lang字段(或复用Accept-Language),值如zh-CN(中文)、en-US(英文),默认值zh-CN
  2. 国际化资源文件:存放不同语言的错误信息模板。
  3. 自定义异常类:携带错误码和参数,便于匹配国际化信息。
  4. 语言解析工具 :从请求头提取语言标识,转换为Locale对象。
  5. 全局异常处理器:捕获异常后,根据语言解析结果加载对应语言的错误信息并返回。
  6. MessageSource配置:加载国际化资源文件,支持参数替换。

二、具体实现步骤

1. 配置国际化资源文件

src/main/resources下创建i18n目录,存放多语言配置文件:

  • messages_zh_CN.properties(中文)

    ini 复制代码
    # 业务异常
    error.user.not.found=用户不存在,用户ID:{0}
    error.param.invalid=参数无效,参数名:{0}
    # 系统异常
    error.system.error=系统内部错误,请稍后重试
  • messages_en_US.properties(英文)

    ini 复制代码
    # 业务异常
    error.user.not.found=User not found, User ID: {0}
    error.param.invalid=Invalid parameter, Parameter name: {0}
    # 系统异常
    error.system.error=System internal error, please try again later

2. 自定义业务异常类

创建BusinessException,用于抛出业务相关异常,携带错误码和参数:

scala 复制代码
package com.example.demo.exception;

import lombok.Getter;

/**
 * 自定义业务异常
 */
@Getter
public class BusinessException extends RuntimeException {
    // 错误码(对应国际化配置文件的key)
    private final String errorCode;
    // 错误信息参数(用于替换国际化模板中的占位符)
    private final Object[] args;

    public BusinessException(String errorCode) {
        this(errorCode, null);
    }

    public BusinessException(String errorCode, Object... args) {
        super(errorCode);
        this.errorCode = errorCode;
        this.args = args;
    }
}

3. 语言解析工具类

创建LocaleUtils,从Http请求头解析语言标识,转换为Locale

java 复制代码
package com.example.demo.utils;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Locale;

/**
 * 语言解析工具类
 */
public class LocaleUtils {
    // 请求头中语言字段名(自定义,也可复用Accept-Language)
    private static final String LANG_HEADER = "lang";
    // 默认语言
    private static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;

    /**
     * 从请求头解析Locale
     */
    public static Locale getLocaleFromRequest(HttpServletRequest request) {
        if (request == null) {
            return DEFAULT_LOCALE;
        }
        // 获取请求头中的lang值
        String lang = request.getHeader(LANG_HEADER);
        if (lang == null || lang.trim().isEmpty()) {
            return DEFAULT_LOCALE;
        }
        // 解析lang值(支持zh-CN、en-US、zh、en等格式)
        String[] langParts = lang.split("-");
        return switch (langParts.length) {
            case 1 -> new Locale(langParts[0]); // 如zh -> Locale("zh")
            case 2 -> new Locale(langParts[0], langParts[1]); // 如zh-CN -> Locale("zh", "CN")
            default -> DEFAULT_LOCALE;
        };
    }
}

4. 配置MessageSource(加载国际化资源)

在Spring Boot配置类中注册MessageSource Bean,加载国际化资源文件:

java 复制代码
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;

import java.nio.charset.StandardCharsets;
import java.util.Locale;

/**
 * 国际化配置
 */
@Configuration
public class I18nConfig {

    /**
     * 配置MessageSource,加载国际化资源文件
     */
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        // 指定资源文件基础名(i18n目录下的messages)
        messageSource.setBasename("i18n/messages");
        // 设置编码,避免中文乱码
        messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
        // 默认语言
        messageSource.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        // 缓存时间(秒),开发时设为0,生产可设为3600
        messageSource.setCacheSeconds(0);
        return messageSource;
    }

    /**
     * 配置LocaleResolver(可选,复用Accept-Language时生效)
     */
    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
        resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return resolver;
    }
}

5. 全局异常处理器

创建GlobalExceptionHandler,捕获异常并返回对应语言的错误信息:

java 复制代码
package com.example.demo.exception;

import com.example.demo.utils.LocaleUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Locale;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
@AllArgsConstructor
public class GlobalExceptionHandler {

    // 注入国际化消息源
    private final MessageSource messageSource;

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> handleBusinessException(BusinessException e, HttpServletRequest request) {
        // 解析请求头的语言
        Locale locale = LocaleUtils.getLocaleFromRequest(request);
        // 从国际化配置中获取对应语言的错误信息
        String errorMessage = messageSource.getMessage(
                e.getErrorCode(), // 错误码(对应配置文件的key)
                e.getArgs(),      // 占位符参数
                e.getErrorCode(), // 默认值(配置文件无该key时使用)
                locale            // 语言
        );
        return Result.fail(HttpStatus.BAD_REQUEST.value(), errorMessage);
    }

    /**
     * 处理系统异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> handleSystemException(Exception e, HttpServletRequest request) {
        Locale locale = LocaleUtils.getLocaleFromRequest(request);
        String errorMessage = messageSource.getMessage(
                "error.system.error",
                null,
                "System internal error",
                locale
        );
        // 打印系统异常栈(生产环境可接入日志框架)
        e.printStackTrace();
        return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), errorMessage);
    }

    /**
     * 统一返回结果封装
     */
    @Data
    @AllArgsConstructor
    public static class Result<T> {
        private int code;       // 状态码
        private String message; // 错误信息
        private T data;         // 数据(异常时为null)

        public static <T> Result<T> fail(int code, String message) {
            return new Result<>(code, message, null);
        }
    }
}

6. 接口示例(测试异常返回)

创建UserController,模拟查询用户接口,不存在时抛业务异常:

kotlin 复制代码
package com.example.demo.controller;

import com.example.demo.exception.BusinessException;
import com.example.demo.exception.GlobalExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试接口
 */
@RestController
@RequestMapping("/users")
public class UserController {

    /**
     * 根据用户ID查询用户
     */
    @GetMapping("/{userId}")
    public GlobalExceptionHandler.Result<?> getUser(@PathVariable Long userId) {
        // 模拟用户不存在的场景
        if (userId <= 0) {
            // 抛业务异常,携带错误码和参数(用户ID)
            throw new BusinessException("error.user.not.found", userId);
        }
        return new GlobalExceptionHandler.Result<>(200, "success", "用户信息:" + userId);
    }
}

三、测试验证

使用Postman/Curl调用接口,通过请求头lang指定语言:

1. 测试中文返回(lang=zh-CN)

  • 请求:

    bash 复制代码
    GET http://localhost:8080/users/-1
    Header: lang=zh-CN
  • 响应:

    json 复制代码
    {
      "code": 400,
      "message": "用户不存在,用户ID:-1",
      "data": null
    }

2. 测试英文返回(lang=en-US)

  • 请求:

    bash 复制代码
    GET http://localhost:8080/users/-1
    Header: lang=en-US
  • 响应:

    json 复制代码
    {
      "code": 400,
      "message": "User not found, User ID: -1",
      "data": null
    }

3. 测试默认语言(不传递lang)

  • 请求:

    bash 复制代码
    GET http://localhost:8080/users/-1
  • 响应:

    json 复制代码
    {
      "code": 400,
      "message": "用户不存在,用户ID:-1",
      "data": null
    }

四、扩展说明

  1. 复用Accept-Language :若想复用HTTP标准头Accept-Language,只需修改LocaleUtils中的LANG_HEADERAccept-Language,并适配解析逻辑(Accept-Language格式如zh-CN,zh;q=0.9,en;q=0.8)。

  2. 更多语言支持 :新增messages_ja_JP.properties(日语)等配置文件,即可支持更多语言,无需修改代码。

  3. 错误码规范 :建议将错误码枚举化(如ErrorCode.USER_NOT_FOUND),避免硬编码。

  4. 生产环境优化

    1. 异常栈信息不要返回给前端,仅打印到日志;
    2. MessageSourcecacheSeconds设为3600,提升性能;
    3. 接入日志框架(如Logback/Log4j2)记录异常详情。

五、核心依赖(pom.xml)

确保Spring Boot基础依赖已引入:

xml 复制代码
<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>
</dependencies>

该方案实现了异常信息的国际化,符合RESTful接口设计规范,且易于扩展和维护。

相关推荐
Master_Azur2 分钟前
java内部类与匿名内部类
后端
开心就好20258 分钟前
不依赖 Mac 也能做 iOS 开发?跨设备开发流程
后端·ios
一直都在5729 分钟前
线程间的通信
java·jvm
一只叫煤球的猫9 分钟前
RAG 如何落地?从原理解释到工程实现
人工智能·后端·ai编程
卷心菜投手ovo20 分钟前
一个页面支持自定义字段,后端该怎么设计数据库?
后端
隔壁家滴怪蜀黍25 分钟前
AgentScope MsgHub 多智能体通信机制详解
后端
孟陬25 分钟前
国外技术周刊 #3:“最差程序员”带动高效团队、不写代码的创业导师如何毁掉创新…
前端·后端·设计模式
GIOTTO情28 分钟前
Infoseek危机公关全链路技术解析:基于近期热点舆情的落地实践
java
Cosolar30 分钟前
Transformer训练与生成背后的数学基础
人工智能·后端·开源
Mr.45671 小时前
Spring Boot集成Redis:单机、哨兵、集群三种模式统一配置实战
spring boot·redis·bootstrap