TDengine 资深研发整理:基于 SpringBoot 多语言实现 API 返回消息国际化

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: 为了帮助开发者更好地进行 SpringBoot 的开发,避免开发盲点,我们将 TDengine 资深研发所做的内部分享——《SpringBoot 多语言支持方案》进行了相关整理,给到有需要的开发者参考。

作为一款在 Java 开发社区中广受欢迎的技术框架,SpringBoot 在开发者和企业的具体实践中应用广泛。具体来说,它是一个用于构建基于 Java 的 Web 应用程序和微服务的框架,通过简化开发流程、提供约定大于配置的原则以及集成大量常用库和组件,SpringBoot 能够帮助开发者更快速、更高效地构建应用程序。


为了帮助开发者更好地进行 SpringBoot 的开发,避免开发盲点,我们将 TDengine 资深研发所做的内部分享——《SpringBoot 多语言支持方案》进行了相关整理,给到有需要的开发者参考。


添加依赖

首先,SpringBoot 作为一个强大的 Java 开发脚手架工具框架,已经提供了多语言定义、解析底层工具,我们只需要在项目依赖中引入 spring-boot-starter 和 spring-boot-autoconfigure 两个包。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

分析 spring-boot-autoconfigure 的源码我们可以看到,在 MessageSourceAutoConfiguration 类中,默认已经自动装配了 MessageSource 对象。


添加多语言 message 配置文件

在 IDEA 中我们只需要在 resources 资源包上右键:新建–>资源包,在弹出窗口填写资源包名如:messages 选择区域设置,默认的有 en、zh_CN、zh_TC 选项。

添加完即可在 resources 包内看到绑定的多语言文件。

注意:在配置文件里查看编辑中文,需要在 IDEA 中修改 message 配置文件。

在配置文件中我们添加 message ,格式为:{code}={message}

METHOD_NOT_ALLOWED=Http method is not supported!
INTERFACE_NOT_FOUND=Interface does not exist!
UNSUPPORTED_MEDIA_TYPE=Not supported MediaType!
ILLEGAL_REQUEST=Illegal request!
SERVICE_UNAVAILABLE=Server resources are unavailable!
SERVER_ERROR=Sorry, an internal server error occurred, please try again later.
INTERNAL_SERVER_ERROR=Internal Server Error.
field.validity.check.failed=Field validity check failed!
bill.account.not-found=bill account not found!
grant.role-group.failed=grant role to group failed!
grant.role-user.failed=grant role to user failed!
add.user-group.failed=add user to group failed!
del.user-group.failed=delete user from group failed!
create.org.failed=create organization failed!
cannot.visit.org=you cannot visit this organization!
wrong.value.parameter=wrong value for parameter!
role.not-found=role not found!
role.update.failed=update role failed!
role.delete.failed=can not delete role!
account.in.arrears=The account is in arrears. Please recharge and try again!

如何使用公共 jar 包内 i18n 资源文件

  • 创建公共资源包 i18n 目录:在 commons 包里添加一个文件夹 i18n-base,这里可以通过一个文件夹避免资源包的覆盖。

  • 依赖 commons 包的模块,在 yaml 配置文件中添加路径。
spring:
  messages:
    basename: i18n-base/messages,messages

以上,我们的多语言框架支持配置、初始化已经完成,接下来就是如何在业务中使用了。


在模块中使用多语言消息

首先我们来看一个典型的 API 服务请求响应流程。客户端发出一个接口请求,会经过多个过滤器进行身份认证、API 接口鉴权认证、权限识别,验证通过后即可进入业务逻辑,最后通过接口返回。返回结果有两种:


  • 过滤器认证失败直接返回包装结果 BaseApiResponse
  • 认证通过进入业务逻辑,这里又包含两种情况:
  • 业务异常,统一通过 GlobalExceptionHandler 拦截,最后由 ResponseAdvice 处理最终返回结果;
  • 无异常,返回业务数据由 ResponseAdvice 处理最终返回结果。


一般来说,外层可以通过 ErrorHandler 捕获整个流程的异常,包括拦截器、框架层的调用出现的异常,最终由 ResponseAdvice 统一处理并最终返回结果。

整个流程如下图:

基于这个业务处理流程我们来封装异常信息国际化的逻辑,如下:

定义多语言 message 获取 LocaleMessageProvider

  • 定义接口
public interface LocaleMessageProvider {
  String get(String msgCode, Object... args);
}
  • 配置实现类
@Bean
public LocaleMessageProvider localeMessageProvider(MessageSource messageSource){
  return (msgCode, args) -> {
    Locale locale = LocaleContextHolder.getLocale();
    return messageSource.getMessage(msgCode,args,locale);
  };
}
  • 在返回结构体中使用 LocaleMessageProvider 获取 message;在 ResponseBodyAdvice 可以为每个 Response 对象设置 messageProvider。
BaseApiResponse.class
private LocaleMessageProvider messageProvider;
public String getmsg() {
  String localeMsg = msg;
  if (messageProvider != null){
    if (StringUtils.hasText(this.msgCode)){
      try {
        localeMsg = messageProvider.get(this.msgCode, getArgs());
      } catch (Exception e) {
        if (!StringUtils.hasText(localeMsg)){
          localeMsg = this.msgCode;
        }
      }
  if (!StringUtils.hasText(localeMsg)){
    localeMsg = StringUtils.hasText(this.msgCode) ? this.msgCode : localeMsg;
  }
  return  localeMsg;
}
public class ResponseAdvice implements ResponseBodyAdvice{
 @Override
 public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType,
    @NotNull MediaType selectedContentType, @NotNull Class selectedConverterType,
    @NotNull ServerHttpRequest request,
    @NotNull ServerHttpResponse response) {
  int code = ServiceInfoEnum.valueOf(key).getServiceCode() * 1000 + 200;
  if (body instanceof BaseApiResponse) {
    BaseApiResponse res = (BaseApiResponse) body;
    res.setMessageProvider(messageProvider);
 }
}
}

在这里提出一个问题,SpringBoot 框架是如何处理语言设置的?在我们定义的 LocaleMessageProvider 里可以使用 LocaleContextHolder.getLocale() 来获取 Locale。


接下来我们继续遵循 LocaleContextHolder 的方法,可以先尝试从内部 localeContext 对象进行获取,获取不到的话则取 Locale 的缺省值。

org.springframework.context.i18n.LocaleContextHolder.java
public static Locale getLocale() {
 return getLocale(getLocaleContext());
}
public static Locale getLocale(@Nullable LocaleContext localeContext) {
 if (localeContext != null) {
  Locale locale = localeContext.getLocale();
  if (locale != null) {
   return locale;
  }
 }
 return (defaultLocale != null ? defaultLocale : Locale.getDefault());
}

在 Locale 类中,我们看到缺省的 locale 最终从系统变量 user.language 获取,缺省是 en。

java.util.Locale.java
    private static volatile Locale defaultLocale = initDefault();
    private static Locale initDefault() {
    String language, region, script, country, variant;
    Properties props = GetPropertyAction.privilegedGetProperties();
    language = props.getProperty("user.language", "en");
    ......//省略代码
}

接下来我们看下 LocaleContextHolder 中的 Locale 是何时设置的,实际就是在 request 请求过滤器基类 RequestContextFilter 里,通过 request.getLocale() 获取到 request 的 locale,然后使用 LocaleContextHolder 设置到 LocaleContext 中。

RequestContextFilter.java
protected void doFilterInternal(
  HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  throws ServletException, IOException {
 ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
 initContextHolders(request, attributes);
 ......//省略代码
}
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
 LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
 ......//省略代码
}

最终我们看到在 Request 对象里,成功获取了“accept-lunguage” 请求。

org.apache.catalina.connector.Request.java
public Locale getLocale() {
    if (!localesParsed) {
        parseLocales();
    }
    if (locales.size() > 0) {
        return locales.get(0);
    }
    return defaultLocale;
}
protected void parseLocales() {
    ......//省略代码
    TreeMap<Double, ArrayList<Locale>> locales = new TreeMap<>();
    Enumeration<String> values = getHeaders("accept-language");
    while (values.hasMoreElements()) {
        String value = values.nextElement();
        parseLocalesHeader(value, locales);
    }
    for (ArrayList<Locale> list : locales.values()) {
        for (Locale locale : list) {
            addLocale(locale);
        }
     }
}

添加一个多语言消息

  1. 如果是异常消息,定义异常消息编码在代码中 exception 需使用 msgCode,如果是业务包装类型,那 BaseApiResponse 消息也要使用 msgCode
  2. 在 message 配置文件中添加对应的 {code}={message}

至此,我们的异常国际化配置就完成了,在客户端我们只需要在请求里添加一个 header:Accept-Language=zh-CN,就可以验证返回的结果。例如登录出错客户端接收到的信息为:

{
    "code": 500,
    "message": "用户名或者密码错误,请重新输入。",
    "data":{}
}


结语

以上就是基于 SpringBoot 多语言支持方案的完整分享内容,现在你可以操作体验了,希望本篇文章能带给你一些帮助。更多示例可参考:

  • 异常中使用 messageCode
if (pricePlan.getClusterNum() >= 0 && appNum >= pricePlan.getClusterNum()) {
  throw new CommonsException(HttpResponseStatus.PAYMENT_REQUIRED.code(),
  "price.plan.limit.instance.number",
      "instance number is over limit!");
}
  • 国际化文件中添加 message
#messages_en.properties
price.plan.limit.instance.number=instance number is over limit
#messages_zh_CN.properties
price.plan.limit.instance.number=实例数量超过限制

如果你在实操过程中还遇到了其他技术问题,或者正面临着时序数据的处理难题,也可以联系我们,和 TDengine 的技术研发人员进行直接沟通。


关于 TDengine

TDengine 核心是一款高性能、集群开源、云原生的时序数据库Time Series DatabaseTSDB),专为物联网、工业互联网、电力、IT 运维等场景设计并优化,具有极强的弹性伸缩能力。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本,是一个高性能、分布式的物联网、工业大数据平台。当前 TDengine 主要提供两大版本,分别是支持私有化部署的 TDengine Enterprise 以及全托管的物联网、工业互联网云服务平台 TDengine Cloud,两者在开源时序数据库 TDengine OSS 的功能基础上有更多加强,用户可根据自身业务体量和需求进行版本选择。

目录
相关文章
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
60 4
|
2月前
|
Java API 数据库
如何使用Spring Boot构建RESTful API,以在线图书管理系统为例
【10月更文挑战第9天】本文介绍了如何使用Spring Boot构建RESTful API,以在线图书管理系统为例,从项目搭建、实体类定义、数据访问层创建、业务逻辑处理到RESTful API的实现,详细展示了每个步骤。通过Spring Boot的简洁配置和强大功能,开发者可以高效地开发出功能完备、易于维护的Web应用。
81 3
|
2月前
|
IDE Java API
基于Spring Boot REST API设计指南
【10月更文挑战第4天】 在现代的软件开发中,RESTful API已经成为了构建网络应用的标准之一。它通过HTTP协议提供了与资源交互的方式,使得不同的应用程序能够进行数据交互。Spring Boot作为一个功能强大的框架,它简化了配置和开发流程,成为了构建RESTful API的理想选择。本文将详细介绍如何在Spring Boot中设计和实现高质量的RESTful API,并提供一些最佳实践。
60 1
|
6天前
|
存储 安全 Java
Spring Boot 编写 API 的 10条最佳实践
本文总结了 10 个编写 Spring Boot API 的最佳实践,包括 RESTful API 设计原则、注解使用、依赖注入、异常处理、数据传输对象(DTO)建模、安全措施、版本控制、文档生成、测试策略以及监控和日志记录。每个实践都配有详细的编码示例和解释,帮助开发者像专业人士一样构建高质量的 API。
|
2月前
|
缓存 Java API
基于Spring Boot REST API设计指南
【10月更文挑战第11天】 在构建现代Web应用程序时,RESTful API已成为一种标准,使得不同的应用程序能够通过HTTP协议进行通信,实现资源的创建、读取、更新和删除等操作。Spring Boot作为一个功能强大的框架,能够轻松创建RESTful API。本文将详细介绍如何在Spring Boot中设计和实现高质量的RESTful API。
141 61
|
28天前
|
Java 测试技术 API
详解Swagger:Spring Boot中的API文档生成与测试工具
详解Swagger:Spring Boot中的API文档生成与测试工具
38 4
|
1月前
|
API 开发工具 开发者
探究亚马逊国际获得AMAZON商品详情 API 接口功能、作用与实际应用示例
亚马逊提供的Amazon Product Advertising API或Selling Partner API,使开发者能编程访问亚马逊商品数据,包括商品标题、描述、价格等。支持跨境电商和数据分析,提供商品搜索和详情获取等功能。示例代码展示了如何使用Python和boto3库获取特定商品信息。使用时需遵守亚马逊政策并注意可能产生的费用。
|
1月前
|
前端开发 Java Spring
SpringBoot项目thymeleaf页面支持词条国际化切换
SpringBoot项目thymeleaf页面支持词条国际化切换
76 2
|
1月前
|
XML API 开发者
亚马逊国际获得AMAZON商品详情 API接口
要获取亚马逊国际商品详情API接口,需先访问亚马逊开发者中心了解API文档,注册账号并创建应用获取API权限及密钥。接着,按文档构建请求URL,使用编程语言发送GET请求,接收并解析XML响应,从中提取商品详情信息,如名称、价格等,最终整合至应用中实现功能。如有疑问,欢迎联系。
|
2月前
|
安全 Java API
基于Spring Boot REST API设计指南
【10月更文挑战第10天】 在现代Web应用开发中,RESTful API扮演着至关重要的角色。Spring Boot作为一个高效、便捷的Java开发框架,为构建RESTful API提供了强大的支持。本文将分享基于Spring Boot的REST API设计指南,涵盖从项目初始化到API文档配置的全过程。
65 0