前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零玩转后端接口数据交互国际化

从零玩转后端接口数据交互国际化

原创
作者头像
Blue_007
修改2023-11-09 08:13:57
1.8K7
修改2023-11-09 08:13:57
举报
文章被收录于专栏:代码生涯代码生涯

🎑 一、前言

当我们的项目涉及到多语言支持时,身为后端开发的我们,接口数据国际化便是我们必须攻克的问题。

SpringBoot提供了强大的国际化(i18n)支持,允许开发者为不同的地区和语言提供定制的文本资源。

那么就让我们一起 “撕开接口数据国际化的面纱”,深入探讨如何在 SpringBoot 应用程序中实现国际化,以满足全球用户的多语言需求。

🎃 二、何为国际化

✨ 2.1 什么是国际化

国际化,也叫i18n,为什么叫i18n呢?

这是因为国际化的英文单词是internationalization ,i和n之间包含了18个单词。 就如我们的k8s一样,k和s之间包含了8个单词(很随性🥰)


🎉 2.2 为什么要使用国际化

一个网站或应用,它原本只面向国内用户提供服务,页面显示和各种操作提示都只有国语。后来随着它的业务扩大,功能越做越多,用户也越来越多,开始走向国际平台了,那么它的用户类型就不只是国内了,可能有法国、美国、日本等等国家的用户。

如果这时网站或应用的显示和各种操作还是中文(或只有一国语言),那么其他国家用户可能完全看不懂网站或应用或者操作困难

那么它对客户的友好度是不是就会大大降低?是不是就会无法留住这类客户?

那么对于这种场景现在国际化就非常重要。


🎍 2.3 国际化细分

对于我们的项目而言,国际化可以分为前端和后端两个部分:

前端国际化

前端国际化主要关注页面的显示和用户界面的本地化。它涉及将应用程序的界面元素,如文本、标签、按钮等,根据用户的语言和地区进行翻译和适配。前端国际化通常使用资源文件、语言包或翻译服务来存储和管理不同语言的文本。前端开发人员可以通过使用国际化框架或库,如React Intl、Vue I18n或Angular i18n等,来实现前端国际化功能。

后端国际化

后端国际化主要关注处理与业务逻辑和数据相关的国际化问题。这包括但不限于日期和时间格式、货币符号、数字格式、排序规则、接口提示信息等。后端国际化的目标是确保应用程序能够适应不同的语言和地区,并提供正确的本地化数据。后端国际化可以通过使用国际化库或框架,如SpringBoot I18n,来实现后端国际化功能。

总之,前端国际化主要关注页面显示和用户界面的本地化,而后端国际化则处理与业务逻辑和数据相关的国际化问题。两者通常需要协同工作,以实现完整的国际化功能。


🏆 三、小知识

在开始实现后端接口国际化之前,我们先来了解一些小知识。

🛒 3.1 Locale对象

需要支持国际化,得先知道选择的是哪种地区的哪种语言,java中使用java.util.Locale表示地区语言,这个对象内部包含了国家和语言的信息。

Locale中最常用的构造方法:

代码语言:java
复制
public Locale(String language, String country) {
    this(language, country, "");
}

构造方法有两个参数:language:语言、country:国家

这两个参数的值不是乱写的,国际上有统一的标准,如:zh-CN表示中国大陆地区的中文,zh-TW表示中国台湾地区的中文,en-US表示美国地区的英文,en-GB表示英国地区的英文等等。通过语言国家构造Locale对象,比如Locale locale = new Locale("zh", "CN");,表示中国大陆地区的中文。


Locale类中已经创建好了很多常用的Locale对象,直接可以拿过来用,随便列几个看一下:

代码语言:java
复制
static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN");
static public final Locale TRADITIONAL_CHINESE = createConstant("zh", "TW");
static public final Locale FRANCE = createConstant("fr", "FR");
static public final Locale GERMANY = createConstant("de", "DE");

🥼 3.2 MessageSource接口

这是 Spring 国际化的核心接口,其定义如下:

代码语言:java
复制
public interface MessageSource {
    /**
     * 获取国际化信息
     */
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    /**
     * 与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常
     */
    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

    /**
     * @param MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个方法相同
     */
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

MessageSource接口提供了三个获取国际化消息的方法,其主要是根据 Locale 信息获取对应的国际化消息的集合,然后根据 code 获取对应的消息,并且通过提供的参数 args 还可以对获取后的消息进行格式化。

具体参数含义如下:

参数名

含义

code

表示国际化资源中的属性名

args

为消息中的参数填充的值

defaultMessage

默认的消息,如果没有找到将返回默认消息

resolvable

消息参数,封装了 code、args、defaultMessage

locale

表示本地化对象


常见3个实现类:

类名

含义

ResourceBundleMessageSource

这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化文件

ReloadableResourceBundleMessageSource

这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新国际化文件的信息

StaticMessageSource

它允许通过编程的方式提供国际化信息。


重点:我们在项目中会创建 MessageSource接口,但不管使用哪个实现类或者我们自定义的类,都要将Bean名称设置为messageSource

加载ApplicationContext时,自动搜索上下文中定义的MessagesSource Bean(名称必须为messageSource)。 如果无法找到消息的任何源,则实例化一个空的messageSource。


🎁 3.3 LocaleResolver接口

这个接口是用来设置当前会话默认的国际化语言的,其定义如下:

代码语言:java
复制
public interface LocaleResolver {
   /**
    * 根据当前请求解析当前请求的本地化信息
    */
    Locale resolveLocale(HttpServletRequest request);

   /**
    * 设置当前请求、响应的本地化信息
    */
    void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}

resolveLocale方法用于从当前request中解析对应出对应的Locale对象,场景如:

比如一个请求发送到程序中(服务器),我们怎么知道它是哪个国家的呢?难道要通过请求拿到ip,然后根据ip去解析去对应的地区?不不,那样太麻烦了。

Spring提供LocaleResolver接口的做用是解析客戶端使用的地区,我们可以在请求头部或者请求url传递对应的语言,LocaleResolver便可根据规则创建对应的Locale对象


常见4个实现类:

类名

含义

AcceptHeaderLocalResovler

通过请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8来决定使用具体的Locale实例,其实AcceptHeaderLocalResovler就是使用HttpServletRequest实例中的Locale实例,在进入DispatcherServlet的时候HttpServletRequest实例里面就已经有Locale实例了,可以通过request.getLocale();来获取Locale实例,HttpServletRequest里面的Locale实例就是使用请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8来构建的,比如请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8 就表示zh的权重是1,en;q=0.9表示en的权重是0.9,zh-HK;q=0.8就表示zh-HK的权重是0.8,那么我么通过request.getLocale();获取到的就是权重最高的zh,然后就是构建一个zh的Locale实例,那么AcceptHeaderLocalResovler在解析Locale的时候就会返回zh的Locale实例

CookieLocaleResovler

根据用户在Cookie中设置的某参数来进行确定具体的本地化Locale实例

SessionLocaleResovler

根据用户在HttpSession中设置某参数来进行确定具体的本地化Locale实例

FixedLocalResovler

使用jdk自带的默认的Locale实例


🎠 3.4 国际化文件

项目中,在resources目录下创建名为i18n的文件目录,然后我们在i18n目录创建国际化文件

格式为:名称_语言_地区.properties

我们先来创建两种语言,如:

message.properties

代码语言:text
复制
name=您的姓名
text=默认文本

这个文件名称没有指定Local信息,当系统找不到的时候会使用这个默认的

message_cn_ZH.properties:中文【中国】

代码语言:text
复制
name=姓名
text=文本

message_en_GB.properties:英文【英国】

代码语言:text
复制
name=Full name
text=text

我们通过MessageSource接口的getMessage方法传入对应的key(如naem、text),便可以从国际化文件中取值。同时我们还可以指定Locale对象,便能找到对应的国际化文件然后取值。


☕ 四、真实线上解决方案

在解决方案中,会采用同时从数据库和properties文件中读取国际化信息,达到国际化信息高灵活性。

🧤 4.1 创建国际化文件

resources/i181n/messages目录,分别创建三个文件(key的名称推荐大写加下划线的方式)

message.properties:

代码语言:text
复制
OK_NAME=您的姓名
OK_TEXT=默认文本

这个文件名称没有指定Local信息,当系统找不到的时候会使用这个默认的

message_cn_ZH.properties:中文【中国】

代码语言:text
复制
OK_NAME=姓名
OK_TEXT=文本

message_en_GB.properties:英文【英国】

代码语言:text
复制
OK_NAME=Full name
OK_TEXT=text

指定i18n国际化文件路径:

代码语言:yaml
复制
spring:
  messages:
    basename: i18n/messages
    encoding: UTF-8

👔 4.2 创建国际化表

表内容(表就简单创建一个了,这里可以自行完善):

代码语言:sql
复制
CREATE TABLE `ok_i18message` (
  `code` varchar(255) NOT NULL COMMENT '属性名',
  `locale` varchar(100) DEFAULT NULL COMMENT '国家代码',
  `message` varchar(255) DEFAULT NULL COMMENT '对应内容',
  PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表数据:

代码语言:sql
复制
INSERT INTO `ok_i18message` (`code`, `locale`, `message`) VALUES ('OK_PASSWORD', 'cn_ZH', '密码');
INSERT INTO `ok_i18message` (`code`, `locale`, `message`) VALUES ('OK_PASSWORD', 'en_GB', 'password');
INSERT INTO `ok_i18message` (`code`, `locale`, `message`) VALUES ('OK_AGE', 'cn_ZH', '年龄');
INSERT INTO `ok_i18message` (`code`, `locale`, `message`) VALUES ('OK_AGE', 'en_GB', 'age');

实体类:

代码语言:java
复制
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 国际化表
 * @ClassName I18message
 * @Author Blue
 * @Date 2023/11/5
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("ok_i18message")
@ApiModel(description = "国际化表")
public class I18message {

    @ApiModelProperty("属性名")
    private String code;

    @ApiModelProperty("国家代码")
    private String locale;

    @ApiModelProperty("对应内容")
    private String message;

}

mapper类:

代码语言:java
复制
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * 国际化表
 * @ClassName AssetsLogMapper
 * @Author Blue
 * @Date 2023/11/5
 */
@Mapper
public interface I18messageMapper extends MPJBaseMapper<I18message> {
}

案例:使用mybatis-plus来完成对表的crud


🎒 4.3 自定义MessageSource类

我们使用自定义MessageSource类来整合国际化消息,在3.2 MessageSource接口中有说StaticMessageSource实现类可以通过编程的方式提供国际化信息,那为何我们不直接使用它?而是自定义一个类?解释我放在最后了。

MessageSource代码:

代码语言:java
复制
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

// @Component("messageSource"): 也可以在此处指明bean的名称为 messageSource
public class MyMessageSource extends AbstractMessageSource implements InitializingBean {

    // 注入查询接口对象
    @Resource
    private I18messageMapper i18messageMapper;

    /**
     * 这个是用来缓存数据库中获取到的配置的
     * 数据库配置更改的时候可以调用reload方法重新加载
     */
    private static final Map<String, Map<String, String>> LOCAL_CACHE = new ConcurrentHashMap<>();

    /**
     * 程序启动之后,会自动加载
     */
    @Override
    public void afterPropertiesSet() {
        this.reload();
    }

    /**
     * 重新加载消息到该类的Map缓存中
     */
    public void reload() {
        // 清除该类的缓存
        LOCAL_CACHE.clear();
        // 加载所有的国际化资源
        LOCAL_CACHE.putAll(this.loadAllMessageResources());
    }

    /**
     * 重点:加载所有的国际化消息资源
     * 同时从数据库和properties文件中读取国际化信息
     */
    private Map<String, Map<String, String>> loadAllMessageResources() {
        // 从数据库中查询所有的国际化资源
        List<I18message> allLocaleMessage = i18messageMapper.selectList(null);
        if (ObjectUtils.isEmpty(allLocaleMessage)) {
            allLocaleMessage = new ArrayList<>();
        }

        // 将查询到的国际化资源转换为 Map<地区码, Map<code, 信息>> 的数据格式
        Map<String, Map<String, String>> localeMsgMap = allLocaleMessage
                // stream流
                .stream()
                // 分组
                .collect(Collectors.groupingBy(
                        // 根据国家地区分组
                        I18message::getLocale,
                        // 收集为Map,key为code,value为信息
                        Collectors.toMap(
                                I18message::getCode
                                , I18message::getMessage
                        )
                ));

        // 获取国家地区List
        List<Locale> localeList = localeMsgMap.keySet().stream().map(Locale::new).collect(Collectors.toList());
        for (Locale locale : localeList) {

            // 按照国家地区来读取本地的国际化资源文件,我们的国际化资源文件放在i18n文件夹之下
            ResourceBundle resourceBundle = ResourceBundle.getBundle("i18n/messages", locale);
            // 获取国际化资源文件中的key和value
            Set<String> keySet = resourceBundle.keySet();

            // 将 code=信息 格式的数据收集为 Map<code,信息> 的格式
            Map<String, String> msgFromFileMap = keySet.stream()
                    .collect(
                            Collectors.toMap(
                                    Function.identity(),
                                    resourceBundle::getString
                            )
                    );

            // 将本地的国际化信息和数据库中的国际化信息合并
            Map<String, String> localeFileMsgMap = localeMsgMap.get(locale.getLanguage());
            localeFileMsgMap.putAll(msgFromFileMap);
            localeMsgMap.put(locale.getLanguage(), localeFileMsgMap);
        }

        return localeMsgMap;
    }

    /**
     * 缓存Map中加载国际化资源
     */
    private String getSourceFromCacheMap(String code, Locale locale) {
        String language = ObjectUtils.isEmpty(locale)
                ? LocaleContextHolder.getLocale().getLanguage() : locale.getLanguage();

        // 获取缓存中对应语言的所有数据项
        Map<String, String> propMap = LOCAL_CACHE.get(language);
        if (!ObjectUtils.isEmpty(propMap) && propMap.containsKey(code)) {
            // 如果对应语言中能匹配到数据项,那么直接返回
            return propMap.get(code);
        }

        // 如果找不到国际化消息,就直接返回code
        return code;
    }

    /**
     * 实现方法,供getMessage方法内部使用
     */
    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        String msg = this.getSourceFromCacheMap(code, locale);
        return new MessageFormat(msg, locale);
    }

    /**
     * 实现方法,供getMessage方法内部使用
     */
    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        return this.getSourceFromCacheMap(code, locale);
    }

}

我们自定义了一个MyMessageSource类继承了AbstractMessageSource抽象类 和 实现了InitializingBean接口,从而实现了从数据库获取到的国际化消息本地properties文件中的国际化消息整合的功能。

名称

含义

AbstractMessageSource

抽象类继承了 HierarchicalMessageSource 接口,而HierarchicalMessageSource 接口继承了MessageSource。它是一个支持“配置文件”方式的抽象类,内部提供一个与区域设置无关的公共消息配置文件,消息代码为关键字。

InitializingBean

为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法


可能大家有注意到,我们在继承AbstractMessageSource抽象类后重写了两个方法:resolveCoderesolveCodeWithoutArguments,这两个方法是供getMessage方法内部使用的。

我们进入到AbstractMessageSource源码,查看getMessage方法,在调用它时传入codelocale,它会调用resolveCode方法或者调用resolveCodeWithoutArguments方法去获取消息,最终返回对应的国际化消息。

而这两个方法已经被我们重写,它的数据都从我们自定义的MyMessageSource类的LOCAL_CACHE(map对象)中获取,


⚾ 4.4 国际化区域解析器

LocaleResolver:用来设置当前会话默认的国际化语言

代码语言:java
复制
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * 区域设置解析程序
 * @ClassName MyLocaleResolver
 * @Author Blue
 * @Date 2023/11/6
 */
@Configuration
public class MyLocaleResolver implements LocaleResolver {

   /**
    * 根据当前请求解析当前请求的本地化信息
    */
    @Override
    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
        // 请求url参数
        String l = httpServletRequest.getParameter("lang");
        // 请求头参数
        String header = httpServletRequest.getHeader("lang");
        // 国际化,每一个locale对象都代表一个特定的政治文化,地区和创建方法
        Locale locale = null;
        // 判断请求url参数中是否有带lang参数(优先)
        if (!StrUtil.isEmpty(l)) {
            // 根据下划线分割
            String[] split = l.split("_");
            // 创建国际化对象
            locale = new Locale(split[0], split[1]);

        // 判断请求头参数中是否有带lang参数
        } else if(!StrUtil.isEmpty(header)) {
            // 替换为空
            header = header.replaceAll("\"","");
            // 根据下划线分割
            String[] split = header.split("_");
            // 创建国际化对象
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

   /**
    * 设置当前请求、响应的本地化信息
    */
    @Override
    public void setLocale(HttpServletRequest httpServletRequest, @Nullable HttpServletResponse
            httpServletResponse, @Nullable Locale locale) {
    }

}

👑 4.5 国际化配置类

这里的重点便在于:将我们自定义的MyMessageSource类,交给让spring容器进行创建和管理,并且名称设置为messageSource

代码语言:java
复制
/**
 * Web Mvc 配置程序
 * 解决跨域问题、添加拦截器、配置视图解析器、静态资源处理、国际化等等
 * @ClassName ApplicationConfig
 * @Author Blue
 * @Date 2023/11/5
 */
@Configuration
public class ApplicationConfig implements WebMvcConfigurer {

   /**
    * 区域设置解析器
    */
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResovel();
    }

    /**
     * Spring在启动的时候,加载上下文的时候,会查询查询是否存在容器名称为messageSource的bean
     * 如果没有就会创建一个名为messageSource的bean,然后放在上下文中
     * 我们手动创建一个名为messageSource的bean,替代Spring为我们自动创建
     */
    @Bean
    public MessageSource messageSource() {
        return new MyMessageSource();
    }
    

}

🎁 4.6 国际化服务类

MessageSource进行封装,方便我们使用

代码语言:java
复制
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import java.util.Locale;

/**
 * 区域设置消息源服务
 * @ClassName LocaleMessageSourceService
 * @Author Blue
 * @Date 2022/11/14
 */
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class LocaleMessageSourceService {

    private final MessageSource messageSource;

    /**
     * @param code 对应messages配置的key
     * @return String
     */
    public String getMessage(String code){
        return getMessage(code,null);
    }

    /**
     * @param code 对应messages配置的key
     * @param args 数组参数
     * @return String
     */
    public String getMessage(String code,Object[] args){
        return getMessage(code, args,"");
    }


    /**
     * @param code 对应messages配置的key
     * @param args 数组参数
     * @param defaultMessage 没有设置key的时候的默认值
     * @return String
     */
    public String getMessage(String code,Object[] args,String defaultMessage){
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(code, args, defaultMessage, locale);
    }

}

🎁 4.7 使用场景

使用场景和方式可以有很多,如配合参数校验validator、全局异常、接口信息返回等等,真实项目可能会更复杂,所以我这里简易使用,让大家可以自行发挥

🥙 4.7.1 全局异常
代码语言:java
复制
/**
 * 全局异常处理程序
 * @ClassName GlobalExceptionHandler
 * @Author Blue
 * @Date 2023/11/6
 */
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GlobalExceptionHandler {

    private final LocaleMessageSourceService localeMessageSourceService;

    /**
     * 自定义验证异常处理
     * 国际化安全提示-validation-jsr303
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        // 获取安全参数判断对应的key值
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return Result.fail(message);
    }
    
    /**
     * 自定义验证异常处理
     * Assert - 异常处理
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public Result handleIllegalArgumentException(IllegalArgumentException e) {
        log.error(e.getMessage(), e);
        // getMessage方法的参数就填写对应 国际化文件 或 国际化表 中的code就行
        return Result.fail(localeMessageSourceService.getMessage("OK_NAME"));
    }

    /**
     * 自定义验证异常处理
     * 邮箱格式错误
     */
    @ExceptionHandler({MailSendException.class,SendFailedException.class,SMTPAddressFailedException.class})
    public Result handleMailSendException(Exception e) {
        log.error(e.getMessage(), e);
        // getMessage方法的参数就填写对应 国际化文件 或 国际化表 中的code就行
        return Result.fail(localeMessageSourceService.getMessage("OK_AGE"));
    }

    /**
     * 自定义验证异常处理
     * 有可能是WebSocketServer发送消息错误
     */
    @ExceptionHandler(IOException.class)
    public Result handleIOException(IOException e) {
        log.error(e.getMessage(), e);
        // getMessage方法的参数就填写对应 国际化文件 或 国际化表 中的code就行
        return Result.fail(localeMessageSourceService.getMessage("OK_WS_ERROR"));
    }

    /**
     * 所有异常处理
     */
    @ExceptionHandler
    public Result handlerException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.fail(localeMessageSourceService.getMessage("OK_ERROR"));
    }

}
📯 4.7.2 接口
代码语言:java
复制
@Slf4j
@CrossOrigin
@RefreshScope
@RestController
@Api(tags = "测试接口返回国际化信息")
@RequestMapping("/test")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {

    private final LocaleMessageSourceService localeMessageSourceService;
    private final MyMessageSource myMessageSource;

    @ApiOperation("参数安全")
    @PostMapping("/is")
    public Result is(String password,Integer age) {
        if (StrUtil.isBlank(password)) {
            return Result.fail(localeMessageSourceService.getMessage("OK_PASSWORD"));
        }
        if (ObjectUtil.isEmpty(age)) {
            return Result.fail(localeMessageSourceService.getMessage("OK_AGE"));
        }
        // 业务...
        return Result.succeed("succeed");
    }

    @ApiOperation("重新加载国际化消息")
    @PostMapping("/reloadMessage")
    public void reloadMessage() {
        myMessageSource.reload();
    }

}

当然参数安全可以不用我这么去判断,太长了,我只是为了更直观的表达场景,大家可以去结合参数校验validator,一个注解便可以完成参数安全国际化提示。

这里我就只编写两种使用场景,大家可以动手试试,结合自己业务和思想,让代码更加强大好用!


📖 五、MessageSource说明

4.3 自定义MessageSource类中我们为什么要自定义MessageSource类呢?StaticMessageSource类明明可以它允许通过编程的方式提供国际化信息。

原因如下:

  • StaticMessageSource类也能实现同样的功能,但是不推荐在生产环境中使用,并且不支持国际化消息删除
  • StaticMessageSource适合国际化消息测试,支持硬编码的方法添加国际化消息

我们查看它的源码,可以看到:

源码的注释也提到了:Intended for testing rather than for use in production systems.翻译为中文就是用于测试而不是用于生产系统

并且所有的国际化消息最终都会缓存到messageMap中,由于StaticMessageSource并没有提供清除map数据的方法,因此只有当程序重启,数据库删除的国际化消息才能被反映到messageMap中

📚 六、最后

好了,至此,我们的从零玩转后端接口数据交互国际化完结,希望大家能有所收获!

如果您对本文有任何疑问或需要帮助,请在评论区留言,我会尽力解答。如果本文对您有帮助,请给个赞以示支持,非常感谢!💗

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🎑 一、前言
  • 🎃 二、何为国际化
    • ✨ 2.1 什么是国际化
      • 🎉 2.2 为什么要使用国际化
        • 🎍 2.3 国际化细分
        • 🏆 三、小知识
          • 🛒 3.1 Locale对象
            • 🥼 3.2 MessageSource接口
              • 🎁 3.3 LocaleResolver接口
                • 🎠 3.4 国际化文件
                • ☕ 四、真实线上解决方案
                  • 🧤 4.1 创建国际化文件
                    • 👔 4.2 创建国际化表
                      • 🎒 4.3 自定义MessageSource类
                        • ⚾ 4.4 国际化区域解析器
                          • 👑 4.5 国际化配置类
                            • 🎁 4.6 国际化服务类
                              • 🎁 4.7 使用场景
                                • 🥙 4.7.1 全局异常
                                • 📯 4.7.2 接口
                            • 📖 五、MessageSource说明
                            • 📚 六、最后
                            相关产品与服务
                            容器服务
                            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档