初始代码
This commit is contained in:
22
yunzhupaas-boot-common/yunzhupaas-common-i18n/pom.xml
Normal file
22
yunzhupaas-boot-common/yunzhupaas-common-i18n/pom.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.yunzhupaas</groupId>
|
||||
<artifactId>yunzhupaas-boot-common</artifactId>
|
||||
<version>5.2.0-RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>yunzhupaas-common-i18n</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.yunzhupaas</groupId>
|
||||
<artifactId>yunzhupaas-common-event</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.yunzhupaas.i18n.config;
|
||||
|
||||
import com.yunzhupaas.config.ConfigValueUtil;
|
||||
import com.yunzhupaas.i18n.constant.I18nApiConst;
|
||||
import com.yunzhupaas.i18n.core.DynamicMessageSource;
|
||||
import com.yunzhupaas.i18n.core.MyReloadableResourceBundleMessageSource;
|
||||
import com.yunzhupaas.i18n.provider.*;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.context.MessageSourceProperties;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 国际化语言配置类
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class I18nAutoConfiguration {
|
||||
|
||||
@Primary
|
||||
@Bean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME)
|
||||
public MessageSource messageSource(MessageSourceProperties properties, List<MessageSourceProvider> messageSourceProviders, DynamicMessageSource dynamicMessageSource) {
|
||||
ReloadableResourceBundleMessageSource messageSource = new MyReloadableResourceBundleMessageSource(messageSourceProviders);
|
||||
if (StringUtils.hasText(properties.getBasename())) {
|
||||
messageSource.setBasenames(StringUtils
|
||||
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
|
||||
}
|
||||
if (properties.getEncoding() != null) {
|
||||
messageSource.setDefaultEncoding(properties.getEncoding().name());
|
||||
}
|
||||
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
|
||||
Duration cacheDuration = properties.getCacheDuration();
|
||||
if (cacheDuration != null) {
|
||||
messageSource.setCacheMillis(cacheDuration.toMillis());
|
||||
}
|
||||
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
|
||||
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
|
||||
|
||||
dynamicMessageSource.setParentMessageSource(messageSource);
|
||||
return dynamicMessageSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DynamicMessageSource getDynamicMessageSource(I18nMessageProvider i18nMessageProvider){
|
||||
return new DynamicMessageSource(i18nMessageProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DynamicMessageProvider getDynamicMessageProvider(){
|
||||
return new MyDynamicMessageProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public I18nMessageProvider getI18nMessageProvider(ConfigValueUtil configValueUtil, DynamicMessageProvider dynamicMessageProvider){
|
||||
return new MyI18nMessageProvider(configValueUtil, dynamicMessageProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public I18nApiConst getI18nApiConst(ConfigValueUtil configValueUtil){
|
||||
return new I18nApiConst(configValueUtil.getApiDomain());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.yunzhupaas.i18n.config;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import com.yunzhupaas.event.ProjectEventListener;
|
||||
import com.yunzhupaas.i18n.constant.I18nConst;
|
||||
import com.yunzhupaas.i18n.provider.MyI18nMessageProvider;
|
||||
import com.yunzhupaas.module.ProjectEventInstance;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* 国际化语言Redis缓存监听配置
|
||||
* 收到租户多语言缓存变动时清空租户多语言缓存
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class I18nRefreshListenerConfig {
|
||||
|
||||
@Resource
|
||||
private MyI18nMessageProvider myI18nMessageProvider;
|
||||
|
||||
@ProjectEventListener(channelRegex = {I18nConst.CACHE_KEY_SERVER + ".*", I18nConst.CACHE_KEY_FRONT + ".*"})
|
||||
// @ProjectEventListener(channelSpel = {"#root.channel.startsWith(T(com.yunzhupaas.i18n.constant.I18nConst).CACHE_KEY_SERVER) || #root.channel.startsWith(T(com.yunzhupaas.i18n.constant.I18nConst).CACHE_KEY_FRONT)"})
|
||||
public void onRedisKeySetEvent(ProjectEventInstance redisEvent){
|
||||
String key = redisEvent.getChannel();
|
||||
// 获取多租户编码
|
||||
key = key.replace(I18nConst.CACHE_KEY_SERVER, "").replace(I18nConst.CACHE_KEY_FRONT, "");
|
||||
myI18nMessageProvider.removeTenantCache(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.yunzhupaas.i18n.provider;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.yunzhupaas.base.ActionResult;
|
||||
import com.yunzhupaas.base.UserInfo;
|
||||
import com.yunzhupaas.i18n.constant.I18nApiConst;
|
||||
import com.yunzhupaas.util.Constants;
|
||||
import com.yunzhupaas.util.StringUtil;
|
||||
import com.yunzhupaas.util.UserProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 国际化翻译源提供者
|
||||
*/
|
||||
@Slf4j
|
||||
public class MyDynamicMessageProvider implements DynamicMessageProvider {
|
||||
|
||||
private int timeout = 3000;
|
||||
|
||||
@Override
|
||||
public String getI18nList(Locale locale) {
|
||||
UserInfo userInfo = UserProvider.getUser();
|
||||
String token = userInfo.getToken();
|
||||
if(StringUtil.isNotEmpty(token)) {
|
||||
try (HttpResponse httpResponse = HttpRequest.get(I18nApiConst.i18nListUrl)
|
||||
.timeout(timeout)
|
||||
.setReadTimeout(timeout)
|
||||
.setConnectionTimeout(timeout)
|
||||
.header(Constants.AUTHORIZATION, token)
|
||||
.header(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag())
|
||||
.execute()) {
|
||||
if (httpResponse.isOk()) {
|
||||
String result = httpResponse.body();
|
||||
ActionResult<JSONArray> out = JSON.parseObject(result, new TypeReference<ActionResult<JSONArray>>() {});
|
||||
if (Objects.equals(Constants.SUCCESS, out.getCode())) {
|
||||
JSONArray messages = out.getData();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
JSONObject obj = messages.getJSONObject(i);
|
||||
String code = obj.getString("enCode");
|
||||
String msg = obj.getString("fullName");
|
||||
stringBuilder.append(String.format("%s=%s%n", code, msg));
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("语言翻译内容获取失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.yunzhupaas.i18n.provider;
|
||||
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import com.yunzhupaas.base.UserInfo;
|
||||
import com.yunzhupaas.config.ConfigValueUtil;
|
||||
import com.yunzhupaas.constant.GlobalConst;
|
||||
import com.yunzhupaas.constant.model.MCode;
|
||||
import com.yunzhupaas.util.TenantHolder;
|
||||
import com.yunzhupaas.util.UserProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 国际化语言翻译
|
||||
*/
|
||||
@Slf4j
|
||||
public class MyI18nMessageProvider implements I18nMessageProvider {
|
||||
|
||||
private ConfigValueUtil configValueUtil;
|
||||
|
||||
private final long CACHE_TIME = 48 * 60 * 60000L;
|
||||
|
||||
// 未使用的情况下, 默认48小时失效, 使用后重新计算缓存时效
|
||||
private TimedCache<String, TimedCache<String, Properties>> tenantMessageProperties = CacheUtil.newTimedCache(CACHE_TIME);
|
||||
// 翻译内容提供者
|
||||
private DynamicMessageProvider dynamicMessageProvider;
|
||||
// 租户语言加载锁
|
||||
private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
|
||||
private Properties emptyProperties = new Properties();
|
||||
|
||||
public MyI18nMessageProvider(ConfigValueUtil configValueUtil, DynamicMessageProvider dynamicMessageProvider) {
|
||||
this.configValueUtil = configValueUtil;
|
||||
this.dynamicMessageProvider = dynamicMessageProvider;
|
||||
// 执行定时清理
|
||||
tenantMessageProperties.schedulePrune(CACHE_TIME);
|
||||
}
|
||||
|
||||
public void removeTenantCache(String tenantId) {
|
||||
tenantMessageProperties.remove(tenantId);
|
||||
}
|
||||
|
||||
public void loadTenantMessage(String tenantId, Locale locale) {
|
||||
Properties i18nListProperties = dynamicMessageProvider.getI18nListProperties(locale);
|
||||
TimedCache<String, Properties> tenantProperties = tenantMessageProperties.get(tenantId);
|
||||
if (tenantProperties == null) {
|
||||
tenantProperties = CacheUtil.newTimedCache(CACHE_TIME);
|
||||
tenantMessageProperties.put(tenantId, tenantProperties);
|
||||
}
|
||||
if (i18nListProperties == null) {
|
||||
// 语言加载失败, 一分钟后重新加载, 避免高频请求
|
||||
tenantProperties.put(locale.toLanguageTag(), emptyProperties, 60000L);
|
||||
} else {
|
||||
// 无论是否有内容返回都是加载成功, 如果properties为空没有翻译数据, 服务端新增翻译时会清空缓存重新加载
|
||||
tenantProperties.put(locale.toLanguageTag(), i18nListProperties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MCode getI18nMessage(String code, Locale locale) {
|
||||
UserInfo userInfo = UserProvider.getUser();
|
||||
// 未登录不获取
|
||||
if (userInfo.getToken() == null) {
|
||||
return null;
|
||||
}
|
||||
// 默认租户或者当前租户
|
||||
String tenantId = GlobalConst.DEFAULT_TENANT_VALUE;
|
||||
if (configValueUtil.isMultiTenancy()) {
|
||||
tenantId = TenantHolder.getDatasourceId();
|
||||
}
|
||||
// 开启租户未获取到租户 不进行翻译获取
|
||||
if (tenantId != null) {
|
||||
// 租户配置中的语言配置
|
||||
String languageTag = locale.toLanguageTag();
|
||||
if (!tenantMessageProperties.containsKey(tenantId) || !tenantMessageProperties.get(tenantId).containsKey(languageTag)) {
|
||||
// 租户其他线程正在加载多语言则直接返回
|
||||
ReentrantLock lock = lockMap.computeIfAbsent(tenantId, k -> new ReentrantLock());
|
||||
boolean isLock = false;
|
||||
try {
|
||||
isLock = lock.tryLock();
|
||||
if (isLock) {
|
||||
loadTenantMessage(tenantId, locale);
|
||||
} else {
|
||||
log.debug("[{}]语言加载中, 直接返回", tenantId);
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
if (isLock) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
Properties languageProperty = tenantMessageProperties.get(tenantId).get(languageTag, false);
|
||||
if (languageProperty != null) {
|
||||
Object message = languageProperty.get(code);
|
||||
if (message != null) {
|
||||
return new MCode("", code, message.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user