jjzjj

springcloud微服务国际化

左边的天堂 2024-04-09 原文

目录

一、初探

单体应用完成国际化还是比较简单的,可以看下面的示例代码。
引入必要的依赖

<!-- SpringBoot Web -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Validator -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- i18n -->
<dependency>
   <groupId>org.webjars.bower</groupId>
   <artifactId>jquery-i18n-properties</artifactId>
   <version>1.2.7</version>
</dependency>

创建一个拦截器

import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LocaleInterceptor extends LocaleChangeInterceptor {
    private static final String LOCALE = "Accept-Language";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String newLocale = request.getHeader(LOCALE);
        if (newLocale != null) {
            LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
            if (localeResolver == null) {
                throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
            }
            try {
                localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
            } catch (IllegalArgumentException ignore) {
            }
        }
        return true;
    }
}

创建一个配置类

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

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

@Configuration
public class LocaleConfig implements WebMvcConfigurer{
    /**
     *	默认解析器 其中locale表示默认语言,当请求中未包含语种信息,则设置默认语种
     *	当前默认为简体中文,zh_CN
     */
    @Bean
    public SessionLocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }

    /**
     *  默认拦截器
     *  拦截请求,获取请求头中包含的语种信息并重新注册语种信息
     */
    @Bean
    public WebMvcConfigurer localeInterceptor() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleInterceptor());
            }
        };
    }

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        // 设置消息源
        bean.setValidationMessageSource(resourceBundleMessageSource());
        return bean;
    }

    @Bean
    public MessageSource resourceBundleMessageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setDefaultEncoding(StandardCharsets.UTF_8.toString());
        // 多语言文件地址
        messageSource.addBasenames("i18n/message");
        return messageSource;
    }

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(localValidatorFactoryBean().getValidator());
        return processor;
    }

    @Override
    public Validator getValidator() {
        return localValidatorFactoryBean();
    }
}

然后在resource下创建i18n目录,选中右键 New =>Resource Bundle

填入base name,选择Project locales,再Add All,确定即可。

打开配置文件,填写对应的中英文数据

配置一下application.yml

spring:
  messages:
    basename: i18n.message
    cache-duration: 3600
    encoding: UTF-8

这样基本上就好了,使用也很简单,看下图

二、深入

对于微服务来讲,每个模块单独配置国际化还是很繁琐的事情。所以一般是将国际化存入数据库进行统一管理。而本地缓存使用Redis替换,从而更新国际化之后,相应的模块能同步。

先把原来的LocaleConfigLocaleInterceptor抽离到公共服务,同时增加一个自定义MessageSource

import com.xxx.common.core.domain.RpcResult;
import com.xxx.common.redis.service.RedisService;
import com.xxx.system.api.RemoteLocaleMessageService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.PostConstruct;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Map;

@Slf4j
@Component("messageSource")
public class CustomMessageSource extends AbstractMessageSource {
    public static final String REDIS_LOCALE_MESSAGE_KEY = "i18n_message";

    @Value("${spring.application.name}")
    private String appName;

	/**
	* 这里使用的是dubbo,也可以用open feign
	* 需要引入dubbo的依赖包 spring-cloud-starter-dubbo
	*/
    @DubboReference(version = "1.0.0")
    private RemoteLocaleMessageService i18nMessageMapper;

    @Autowired
    private RedisService redisService;

    @PostConstruct
    public void init() {
        log.info("init i18n message...");
        redisService.deleteObject(REDIS_LOCALE_MESSAGE_KEY + ":" + appName);
        this.reload();
    }

    /**
     * 重新加载消息到该类的Map缓存中
     */
    public Map<String, Map<String, String>> reload() {
        Map<String, Map<String, String>> localeMsgMap = redisService.getCacheMap(REDIS_LOCALE_MESSAGE_KEY + ":" + appName);
        if (localeMsgMap == null || localeMsgMap.isEmpty()) {
            // 加载所有的国际化资源
            localeMsgMap = this.loadAllMessageResources();
            // 缓存到redis
            if (localeMsgMap != null && !localeMsgMap.isEmpty()) {
                redisService.setCacheMap(REDIS_LOCALE_MESSAGE_KEY + ":" + appName, localeMsgMap);
            }
        }
        return localeMsgMap;
    }

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        String msg = this.getSourceFromCacheMap(code, locale);
        return new MessageFormat(msg, locale);
    }

    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        return this.getSourceFromCacheMap(code, locale);
    }

    /**
     * 加载所有的国际化消息资源
     *
     * @return
     */
    private Map<String, Map<String, String>> loadAllMessageResources() {
        // 从数据库中查询所有的国际化资源
        RpcResult<Map<String, Map<String, String>>> rpcResult = i18nMessageMapper.getAllLocaleMessage(appName);
        return rpcResult.getCode() == 200 ? rpcResult.getData() : null;
    }

    /**
     * 缓存Map中加载国际化资源
     *
     * @param code
     * @param locale
     * @return
     */
    private String getSourceFromCacheMap(String code, Locale locale) {
        // 判断如果没有值则会去重新加载数据
        Map<String, Map<String, String>> localeMsgMap = this.reload();
        String language = ObjectUtils.isEmpty(locale) ? LocaleContextHolder.getLocale().getLanguage() : locale.getLanguage();
        // 获取缓存中对应语言的所有数据项
        Map<String, String> propMap = localeMsgMap.get(language);
        if (!ObjectUtils.isEmpty(propMap) && propMap.containsKey(code)) {
            // 如果对应语言中能匹配到数据项,那么直接返回
            return propMap.get(code);
        }
        // 如果找不到国际化消息,就直接返回code
        return code;
    }
}

并对LocaleConfig进行改造

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

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

@Configuration
public class LocaleConfig implements WebMvcConfigurer {

     @Autowired
     private CustomMessageSource customMessageSource;

    /**
     *	默认解析器 其中locale表示默认语言,当请求中未包含语种信息,则设置默认语种
     *	当前默认为SIMPLIFIED_CHINESE,zh_CN
     */
    @Bean
    public SessionLocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }

    /**
     *  默认拦截器
     *  拦截请求,获取请求头中包含的语种信息并重新注册语种信息
     */
    @Bean
    public WebMvcConfigurer localeInterceptor() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleInterceptor());
            }
        };
    }

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        bean.setMessageInterpolator(interpolatorFactory.getObject());
        // 设置消息源
        bean.setValidationMessageSource(resourceBundleMessageSource());
        return bean;
    }

    @Bean
    public MessageSource resourceBundleMessageSource() {
        return customMessageSource;
    }

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(localValidatorFactoryBean().getValidator());
        return processor;
    }

    @Override
    public Validator getValidator() {
        return localValidatorFactoryBean();
    }
}

在需要的模块中引入即可。

Dubbo provider的实现

import com.xxx.common.core.domain.RpcResult;
import com.xxx.system.api.RemoteLocaleMessageService;
import com.xxx.system.entity.SysLocaleMessage;
import com.xxx.system.service.ISysLocaleMessageService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@DubboService(version = "1.0.0")
public class DubboLocaleMessageService implements RemoteLocaleMessageService {
    @Autowired
    private ISysLocaleMessageService localeMessageService;

    private final String COMMON_CONFIG = "common";

	// 这里不能返回List,因为无法序列化,会导致Dubbo异常,所以使用Map
    @Override
    public RpcResult<Map<String, Map<String, String>>> getAllLocaleMessage(String module) {
        // 每个module对应的配置
        SysLocaleMessage localeMessage = new SysLocaleMessage();
        localeMessage.setModule(module);
        List<SysLocaleMessage> list = localeMessageService.queryByParam(localeMessage);
        if (list == null) {
            list = new ArrayList<>();
        }
        // 公共配置
        localeMessage = new SysLocaleMessage();
        localeMessage.setModule(COMMON_CONFIG);
        list.addAll(localeMessageService.queryByParam(localeMessage));
        if (CollectionUtils.isEmpty(list)) {
            return RpcResult.fail("no data!");
        }

        Map<String, Map<String, String>> localeMsgMap = list.stream().collect(Collectors.groupingBy(
                // 根据国家地区分组
                SysLocaleMessage::getLocale,
                // 收集为Map,key为code,msg为信息
                Collectors.toMap(SysLocaleMessage::getCode, SysLocaleMessage::getMsg)
        ));
        return RpcResult.success(localeMsgMap);
    }
}

将国际化的增删改查功能集成到系统管理,就可以通过数据库进行管理了。

对国际化进行增删改后,需要对Redis缓存进行更新

/**
 * 清理Redis所有前缀匹配的缓存
 */
private void clearCache() {
    Collection<String> keys = redisService.keys(CustomMessageSource.REDIS_LOCALE_MESSAGE_KEY + "*");
    keys.stream().forEach(key -> {
        redisService.deleteObject(key);
    });
}

/**
 * 按key清理Redis缓存
 */
private void clearCache(String key) {
    redisService.deleteObject(CustomMessageSource.REDIS_LOCALE_MESSAGE_KEY + ":" + key);
}

之前创建的resouce/i18n目录则可以删除。使用也是和单体应用一样的。

有关springcloud微服务国际化的更多相关文章

  1. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  2. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  3. 【云原生】SpringCloud-Spring Boot Starter使用测试 - 2

    目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现: 创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。 在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringBootStarter是什么? SpringBootStarter是在SpringBoot组件中被提出来的一种概念、简化了很多烦琐的配置、通过引入各种SpringBootStarter包可以快速搭建出一

  4. ruby-on-rails - Rails 国际化 : %{record} is not being translated - 2

    我的rails.pt-BR.yml上有这个:br:errors:format:!'%{attribute}%{message}'messages:restrict_dependent_destroy:one:"Nãoépossívelexcluiroregistropoisexisteum%{record}dependente"many:"Nãoépossívelexcluiroregistropoisexistem%{record}dependentes"在我的模型中,我有这个:has_many:entities,dependent::restrict_with_error每当res

  5. SpringCloud入门实战(七)-Hystrix入门简介 - 2

    📝学技术、更要掌握学习的方法,一起学习,让进步发生👩🏻作者:一只IT攻城狮。💐学习建议:1、养成习惯,学习java的任何一个技术,都可以先去官网先看看,更准确、更专业。💐学习建议:2、然后记住每个技术最关键的特性(通常一句话或者几个字),从主线入手,由浅入深学习。❤️《SpringCloud入门实战系列》解锁SpringCloud主流组件入门应用及关键特性。带你了解SpringCloud主流组件,是如何一战解决微服务诸多难题的。项目demo:源码地址👉🏻SpringCloud入门实战系列不迷路👈🏻:SpringCloud入门实战(一)什么是SpringCloud?SpringCloud入门实战

  6. 【微服务笔记23】使用Spring Cloud微服务组件从0到1搭建一个微服务工程 - 2

    这篇文章,主要介绍如何使用SpringCloud微服务组件从0到1搭建一个微服务工程。目录一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件(2)微服务依赖1.2、搭建注册中心(1)引入依赖(2)配置文件(3)启动类1.3、搭建配置中心(1)引入依赖(2)配置文件(3)启动类1.4、搭建API网关(1)引入依赖(2)配置文件(3)启动类1.5、搭建服务提供者(1)引入依赖(2)配置文件(3)启动类1.6、搭建服务消费者(1)引入依赖(2)配置文件(3)启动类1.7、运行测试一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件这里主要是使用的SpringCloudNetflix

  7. ruby - 删除非字母数字字符而不删除 ruby 中的国际字符 - 2

    我想删除字符串中的非字母数字字符,但不删除国际字符,如重音字母。我也想保留空白。这是我目前所拥有的:the_string=the_string.gsub(/[^a-z0-9-]/i,'')虽然这确实会删除国际重音字母字符。我使用的解决方案:the_string=the_string.gsub(/[^\p{Alnum}\p{Space}-]/u,'')有效!谢谢。 最佳答案 您可以使用characterproperties这样做:the_string.gsub(/[^\p{Alnum}-]/,'')您可能还想使用\p{Space}来保

  8. Spring 国际化遇到的坑 No message found under code ‘xxx.xxxx‘ for locale ‘zh_CN‘ - 2

    Spring国际化遇到的坑org.springframework.context.NoSuchMessageException:Nomessagefoundundercode‘xxx.xxxx’forlocale‘zh_CN’背景以前做的项目客户群体只有国内的客户,从来没有考虑过语言文字的问题。这次有一个需求的返回内容,要根据客户设置的语言返回不同的语言。尝试使用了以后,结果发现怎么都不好使,一直报的一个错误如下:org.springframework.context.NoSuchMessageException:Nomessagefoundundercode'user.name'forloc

  9. ruby - 没有 Rails 的国际化? - 2

    只是在没有Rails环境的情况下让I18n工作有困难:irb>require'i18n'=>trueirb>I18n.load_path=Dir['/usr/lib/ruby/gems/1.9.1/gems/rails-i18n-0.6.6/rails/locale/en.yml']=>["/usr/lib/ruby/gems/1.9.1/gems/rails-i18n-0.6.6/rails/locale/en.yml"]irb>I18n.load_path+=Dir['/usr/lib/ruby/gems/1.9.1/gems/rails-i18n-0.6.6/rails/loca

  10. ruby-on-rails - 如何在 Rails 中格式化这个国际电话号码? - 2

    如果我有这样的国际电话号码:0541754301我怎样才能格式化它来产生这样的东西:0541-754-301 最佳答案 您可以使用ActionView::Helpers::NumberHelper中的number_to_phone(number,options={})方法但是,文档指出此方法会将数字格式化为美国电话号码(例如(555)123-9876)。相反,您可以使用thispatch它增加了提供数字分组的能力::groupings-Specifiesalternategroupings(mustspecify3-elementa

随机推荐