♻️ 重构 i18n 功能,新增 common-i18n,ballcat-i18n,移除原先的 extend-i18n,以及 i18n-starter

This commit is contained in:
b2baccline
2021-08-04 22:03:45 +08:00
parent 47f8f2f121
commit ecfa89c7c5
64 changed files with 987 additions and 1600 deletions

View File

@@ -16,10 +16,6 @@
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-spring-boot-starter-i18n</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-spring-boot-starter-web</artifactId>

View File

@@ -1,186 +0,0 @@
package com.hccake.ballcat.admin.i18n.config;
import com.hccake.ballcat.autoconfigure.web.exception.resolver.GlobalHandlerExceptionResolver;
import com.hccake.ballcat.common.core.constant.GlobalConstants;
import com.hccake.ballcat.common.core.exception.BusinessException;
import com.hccake.ballcat.common.core.exception.handler.GlobalExceptionHandler;
import com.hccake.ballcat.common.model.result.R;
import com.hccake.ballcat.common.model.result.SystemResultCode;
import com.hccake.common.i18n.execute.TranslateExecuteWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
/**
* 国际化全局异常处理器
*
* @author Yakir
*/
@Slf4j
@RestControllerAdvice
public class I18NGlobalException extends GlobalHandlerExceptionResolver {
private final GlobalExceptionHandler globalExceptionHandler;
public I18NGlobalException(GlobalExceptionHandler globalExceptionHandler) {
super(globalExceptionHandler);
this.globalExceptionHandler = globalExceptionHandler;
}
@Value("${spring.profiles.active:prod}")
private String profile;
/**
* 异常业务key
*/
@Value("${ballcat.i18n.exceptionKey:exception}")
private String exceptionCode;
public static final String PROD_ERR_MSG = "系统异常,请联系管理员";
public static final String NLP_MSG = "空指针异常!";
/**
* 全局异常捕获
* @param e the e
* @return R
*/
@Override
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R<String> handleGlobalException(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
globalExceptionHandler.handle(e);
// 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
String errorMsg = GlobalConstants.ENV_PROD.equals(profile) ? PROD_ERR_MSG
: (e instanceof NullPointerException ? NLP_MSG : e.getLocalizedMessage());
String exceptionMsg = executeTranslate("global.exception", SystemResultCode.BAD_REQUEST.getCode(), errorMsg);
return R.failed(SystemResultCode.SERVER_ERROR, exceptionMsg);
}
/**
* MethodArgumentTypeMismatchException 参数类型转换异常
* @param e the e
* @return R
*/
@Override
@ExceptionHandler({ MethodArgumentTypeMismatchException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<String> handleMethodArgumentTypeMismatchException(Exception e) {
log.error("请求入参异常 ex={}", e.getMessage());
globalExceptionHandler.handle(e);
String message = GlobalConstants.ENV_PROD.equals(profile) ? PROD_ERR_MSG : e.getMessage();
String exceptionMsg = executeTranslate("method.argument.type.exception", SystemResultCode.BAD_REQUEST.getCode(),
message);
return R.failed(SystemResultCode.BAD_REQUEST, exceptionMsg);
}
/**
* 请求方式有问题 - MediaType 异常 - Method 异常
* @return R
*/
@Override
@ExceptionHandler({ HttpMediaTypeNotSupportedException.class, HttpRequestMethodNotSupportedException.class })
public R<String> requestNotSupportedException(Exception e) {
log.error("请求方式异常 ex={}", e.getMessage());
globalExceptionHandler.handle(e);
String exceptionMsg = executeTranslate("request.not.support.exception", SystemResultCode.BAD_REQUEST.getCode(),
e.getLocalizedMessage());
return R.failed(SystemResultCode.BAD_REQUEST, exceptionMsg);
}
/**
* IllegalArgumentException 异常捕获主要用于Assert
* @param e the e
* @return R
*/
@Override
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<String> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("非法数据输入 ex={}", e.getMessage());
globalExceptionHandler.handle(e);
String exceptionMsg = executeTranslate("illegal.argument.exception", SystemResultCode.BAD_REQUEST.getCode(),
e.getMessage());
return R.failed(SystemResultCode.BAD_REQUEST, exceptionMsg);
}
/**
* validation Exception
* @param exception e
* @return R
*/
@Override
@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<String> handleBodyValidException(BindException exception) {
BindingResult bindingResult = exception.getBindingResult();
String errorMsg = bindingResult.getErrorCount() > 0 ? bindingResult.getAllErrors().get(0).getDefaultMessage()
: "未获取到错误信息!";
log.error("参数绑定异常,ex = {}", errorMsg);
globalExceptionHandler.handle(exception);
String exceptionMsg = executeTranslate("body.valid.exception", SystemResultCode.BAD_REQUEST.getCode(),
errorMsg);
return R.failed(SystemResultCode.BAD_REQUEST, exceptionMsg);
}
/**
* 单体参数校验异常 validation Exception
* @param e the e
* @return R
*/
@Override
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<String> handleValidationException(ValidationException e) {
log.error("参数绑定异常 ex={}", e.getMessage());
globalExceptionHandler.handle(e);
String exceptionMsg = executeTranslate("valid.exception", SystemResultCode.BAD_REQUEST.getCode(),
e.getLocalizedMessage());
return R.failed(SystemResultCode.BAD_REQUEST, exceptionMsg);
}
/**
* 自定义业务异常捕获 业务异常响应码推荐使用200 用 result 结构中的code做为业务错误码标识
* @param e the e
* @return R
*/
@Override
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public R<String> handleBallCatException(BusinessException e) {
log.error("业务异常信息 ex={}", e.getMessage());
globalExceptionHandler.handle(e);
String exceptionMsg = executeTranslate("ballcat.exception", e.getCode(), e.getMessage());
return R.failed(e.getCode(), exceptionMsg);
}
private String executeTranslate(String code, Integer statusCode, String exceptionMsg) {
return TranslateExecuteWrapper.translateText(exceptionCode, code, prodI18nErrorParam(statusCode, exceptionMsg));
}
private Map<String, String> prodI18nErrorParam(Integer code, String exceptionMsg) {
Map<String, String> params = new HashMap<>();
params.put("code", String.valueOf(code));
params.put("msg", exceptionMsg);
return params;
}
}

View File

@@ -1,27 +0,0 @@
package com.hccake.ballcat.admin.i18n.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* i18n admin properties
*
* @author Yakir
*/
@Component
@Data
@ConfigurationProperties(prefix = "ballcat.i18n")
public class I18nAdminProperties {
/**
* 请求头指定语言key
*/
private String langHeader = "lang";
/**
* msg响应key code
*/
private String responseMsgKey = "response_msg";
}

View File

@@ -1,53 +0,0 @@
package com.hccake.ballcat.admin.i18n.config;
import com.hccake.ballcat.common.model.result.R;
import com.hccake.common.i18n.execute.TranslateExecuteWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 只针对R的msg进行国际化处理
*
* @author Yakir
*/
@RequiredArgsConstructor
@RestControllerAdvice
public class I18nResponseAdvice implements ResponseBodyAdvice<R> {
private final I18nAdminProperties i18nAdminProperties;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return isSupport(methodParameter);
}
@Override
public R beforeBodyWrite(R r, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
String responseMsg = TranslateExecuteWrapper.translateText(i18nAdminProperties.getResponseMsgKey(),
r.getMessage());
r.setMessage(responseMsg);
TranslateExecuteWrapper.translateObject(r.getData());
return r;
}
/**
* 仅支持返回值类型为R 与不为错误处理器返回过来的类型
* @param methodParameter 方法参数
* @return true 支持 false 不支持
*/
private boolean isSupport(MethodParameter methodParameter) {
return methodParameter.getMethod().getAnnotation(ExceptionHandler.class) == null
&& methodParameter.getParameterType().isAssignableFrom(R.class);
}
}

View File

@@ -1,58 +0,0 @@
package com.hccake.ballcat.admin.i18n.config;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* 语言解析器 主要负责从头提取语言环境
*
* @author Yakir
*/
@RequiredArgsConstructor
@Configuration
public class LocaleConfig {
private final I18nAdminProperties i18nAdminProperties;
/**
* 区域解析器
* @return @{link LocaleResolver}
*/
@Bean
public LocaleResolver localeResolver() {
MyLocaleResolver localeResolver = new MyLocaleResolver();
return localeResolver;
}
class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getHeader(i18nAdminProperties.getLangHeader());
if (StrUtil.isEmpty(language)) {
// 路径上没有国际化语言参数,采用默认的(从请求头中获取)
return request.getLocale();
}
else {
// 格式语言_国家 en_US
return StringUtils.parseLocale(language);
}
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Locale locale) {
}
}
}

View File

@@ -0,0 +1,44 @@
<?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">
<parent>
<artifactId>ballcat-common</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-common-i18n</artifactId>
<dependencies>
<!-- slf4j日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
package com.hccake.ballcat.common.i18n;
import lombok.RequiredArgsConstructor;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.lang.Nullable;
import java.text.MessageFormat;
import java.util.Locale;
/**
* 动态获取的 MessageSource比如从数据库 或者 redis 中获取 message 信息
*
* @author hccake
*/
@RequiredArgsConstructor
public class DynamicMessageSource extends AbstractMessageSource {
public static final String DYNAMIC_MESSAGE_SOURCE_BEAN_NAME = "dynamicMessageSource";
private final I18nMessageProvider i18nMessageProvider;
@Override
@Nullable
protected MessageFormat resolveCode(String code, Locale locale) {
I18nMessage i18nMessage = i18nMessageProvider.getI18nMessage(code, locale);
if (i18nMessage != null) {
return createMessageFormat(i18nMessage.getMessage(), locale);
}
return null;
}
}

View File

@@ -0,0 +1,15 @@
package com.hccake.ballcat.common.i18n;
import java.lang.annotation.*;
/**
* 标注于需要国际化处理的类上, 配合 {@link I18nField} 使用,在响应时进行国际化处理
* @see I18nResponseAdvice
* @author hccake
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface I18nClass {
}

View File

@@ -0,0 +1,32 @@
package com.hccake.ballcat.common.i18n;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
/**
* 用于标注在需要国际化的 String 类型的属性上,用于标记其需要国际化。 必须在拥有 {@link I18nClass} 注解标记的类上
* @author hccake
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface I18nField {
/**
* <p>
* This is an alias for {@link #code}
* </p>
* @return String
*/
@AliasFor("code")
String value() default "";
/**
* 国际化的唯一标识, 当不传值时,则使用被标注的元素的值作为 code
* @return String
*/
@AliasFor("value")
String code() default "";
}

View File

@@ -0,0 +1,28 @@
package com.hccake.ballcat.common.i18n;
import lombok.Data;
/**
* 对标于 message bundle 的文件消息的抽象
*
* @author hccake
*/
@Data
public class I18nMessage {
/**
* 唯一标识
*/
private String code;
/**
* 消息
*/
private String message;
/**
* 地区语言标签
*/
private String languageTag;
}

View File

@@ -0,0 +1,20 @@
package com.hccake.ballcat.common.i18n;
import java.util.Locale;
/**
* 国际化信息的提供者,使用者实现此接口,用于从数据库或者缓存中读取数据
*
* @author hccake
*/
public interface I18nMessageProvider {
/**
* 获取 I18nMessage 对象
* @param code 国际化唯一标识
* @param locale 语言
* @return 国际化消息
*/
I18nMessage getI18nMessage(String code, Locale locale);
}

View File

@@ -0,0 +1,134 @@
package com.hccake.ballcat.common.i18n;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Locale;
/**
* 利用 ResponseBodyAdvice 对返回结果进行国际化处理
*
* @author Yakir
* @author hccake
*/
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class I18nResponseAdvice implements ResponseBodyAdvice<Object> {
private final MessageSource messageSource;
// todo 配置文件
private Locale defaultLocal = Locale.SIMPLIFIED_CHINESE;
/**
* supports all type
* @param returnType MethodParameter
* @param converterType 消息转换器
* @return always true
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
switchLanguage(body);
return body;
}
/**
* <p>
* 对提供了 {@link I18nClass} 注解的类进行国际化处理,递归检查所有属性。
* </p>
* ps: 仅处理 String 类型,且注解了 {@link I18nField} 的属性
* @param source 当前待处理的对象
*/
public void switchLanguage(Object source) {
if (source == null) {
return;
}
Class<?> sourceClass = source.getClass();
// 只对添加了 I18nClass 注解的类进行处理
I18nClass i18nClass = sourceClass.getAnnotation(I18nClass.class);
if (i18nClass == null) {
return;
}
for (Field field : ReflectUtil.getFields(sourceClass)) {
Class<?> fieldType = field.getType();
Object fieldValue = ReflectUtil.getFieldValue(source, field);
if (fieldValue instanceof String) {
// 若不存在国际化注解 直接跳过
I18nField i18nField = field.getAnnotation(I18nField.class);
if (i18nField == null) {
continue;
}
// 获取国际化的唯一标识
String annotationCode = i18nField.code();
String code = StrUtil.isNotEmpty(annotationCode) ? annotationCode : (String) fieldValue;
if (StrUtil.isEmpty(code)) {
continue;
}
// 把当前 field 的值更新为国际化后的属性
Locale locale = LocaleContextHolder.getLocale();
String message;
try {
message = messageSource.getMessage(code, null, locale);
}
catch (NoSuchMessageException e) {
log.warn("[switchLanguage]未找到对应的国际化配置code: {}, local: {}.切换到默认的语言:{}", code, locale, defaultLocal);
message = messageSource.getMessage(code, null, defaultLocal);
}
ReflectUtil.setFieldValue(source, field, message);
}
else if (fieldValue instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Object> elements = (Collection<Object>) fieldValue;
if (CollectionUtil.isEmpty(elements)) {
continue;
}
// 集合属性 递归处理
for (Object element : elements) {
switchLanguage(element);
}
}
else if (fieldType.isArray()) {
Object[] elements = (Object[]) fieldValue;
if (elements == null || elements.length == 0) {
continue;
}
// 数组 递归处理
for (Object element : elements) {
switchLanguage(element);
}
}
else {
// 其他类型的属性,递归判断处理
switchLanguage(fieldValue);
}
}
}
}

View File

@@ -0,0 +1,41 @@
package com.hccake.ballcat.common.i18n;
import org.springframework.context.HierarchicalMessageSource;
import org.springframework.context.MessageSource;
import org.springframework.context.support.AbstractApplicationContext;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* 用于修改 MessageSource 的层级关系,保证 DynamicMessageSource 在父级位置,减少开销
*
* @author hccake
*/
public class MessageSourceHierarchicalChanger {
@Resource(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME)
private MessageSource messageSource;
@Resource(name = DynamicMessageSource.DYNAMIC_MESSAGE_SOURCE_BEAN_NAME)
private DynamicMessageSource dynamicMessageSource;
/**
* 将 dynamicMessageSource 置为 messageSource 的父级<br/>
* 若 messageSource 非层级,则将 messageSource 置为 dynamicMessageSource 的父级
*/
@PostConstruct
public void changeMessageSourceParent() {
// 优先走 messageSource从资源文件中查找
if (messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hierarchicalMessageSource = (HierarchicalMessageSource) messageSource;
MessageSource parentMessageSource = hierarchicalMessageSource.getParentMessageSource();
dynamicMessageSource.setParentMessageSource(parentMessageSource);
hierarchicalMessageSource.setParentMessageSource(dynamicMessageSource);
}
else {
dynamicMessageSource.setParentMessageSource(messageSource);
}
}
}

View File

@@ -0,0 +1,34 @@
package com.hccake.ballcat.common.i18n;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 国际化信息的提供者,使用者实现此接口,用于从数据库或者缓存中读取数据
*
* @author hccake
*/
public class DefautlI18nMessageProvider {
private static final Map<String, I18nMessage> map = new ConcurrentHashMap<>();
static {
I18nMessage i18nMessage = new I18nMessage();
i18nMessage.setMessage("你好啊");
i18nMessage.setCode("test");
i18nMessage.setLanguageTag("zh-CN");
map.put("test:zh-CN", i18nMessage);
I18nMessage i18nMessage2 = new I18nMessage();
i18nMessage2.setMessage("Hello");
i18nMessage2.setCode("test");
i18nMessage2.setLanguageTag("en-US");
map.put("test:en-US", i18nMessage2);
}
public I18nMessage getI18nMessage(String code, Locale locale) {
String languageTag = locale.toLanguageTag();
return map.get(code + ":" + languageTag);
}
}

View File

@@ -7,10 +7,15 @@
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-common-model</artifactId>
<dependencies>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-i18n</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
@@ -23,16 +28,6 @@
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-extend-i18n-annotation</artifactId>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</dependencies>
</project>

View File

@@ -1,6 +1,6 @@
package com.hccake.ballcat.common.model.result;
import com.hccake.common.i18n.annotation.I18nField;
import com.hccake.ballcat.common.i18n.I18nClass;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
@@ -14,6 +14,7 @@ import java.io.Serializable;
* @param <T>
* @author Hccake
*/
@I18nClass
@Getter
@Setter
@ToString

View File

@@ -22,7 +22,8 @@
<module>ballcat-common-idempotent</module>
<module>ballcat-common-log</module>
<module>ballcat-common-redis</module>
</modules>
<module>ballcat-common-i18n</module>
</modules>

View File

@@ -124,6 +124,11 @@
<artifactId>ballcat-common-desensitize</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-i18n</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-idempotent</artifactId>
@@ -235,21 +240,6 @@
<artifactId>ballcat-spring-boot-starter-xss</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-extend-i18n-annotation</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-extend-i18n-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-spring-boot-starter-i18n</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-admin-i18n</artifactId>
@@ -350,6 +340,22 @@
<artifactId>ballcat-log-model</artifactId>
<version>${revision}</version>
</dependency>
<!-- i18n 国际化模块 -->
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-i18n-controller</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-i18n-biz</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-i18n-model</artifactId>
<version>${revision}</version>
</dependency>
<!--swagger注解-->
<dependency>

View File

@@ -1,31 +0,0 @@
package com.hccake.common.i18n.annotation;
import java.lang.annotation.*;
/**
* i18n field annotation
* @author Yakir
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface I18nField {
/**
* 业务
* @return 业务码
*/
String businessCode();
/**
* 范围值 若指定 则对此范围内的值进行国际化 不在范围的则使用默认值
* @return 范围值
*/
String[] rangeValue() default {};
/**
* 默认值
*/
String defaultValue() default "";
}

View File

@@ -1,34 +0,0 @@
package com.hccake.common.i18n;
import com.hccake.common.i18n.model.I18nItem;
import java.util.List;
/**
* i18n 数据提供 主要负责从数据库中提取数据
*
* @author Yakir
* @since 2021/3/30
*/
public interface I18nDataProvider {
/**
* 指定业务 业务码 语言环境
* @param systemName
* @param businessCode
* @param code
* @param language
* @return
*/
I18nItem selectOne(String systemName, String businessCode, String code, String language);
/**
* 查询列表 指定code 查询出所有语言环境
* @param systemName
* @param businessCode
* @param code
* @return
*/
List<I18nItem> selectListByCode(String systemName, String businessCode, String code);
}

View File

@@ -1,69 +0,0 @@
package com.hccake.common.i18n;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* i18n properties
*
* @author Yakir
*/
@Data
@ConfigurationProperties(prefix = "ballcat.i18n")
public class I18nProperties {
/**
* 系统名称
*/
private String systemName = "test-item";
/**
* 缓存空值标记
*/
private String nullValue = "N_V";
/**
* 执行器 主要用来对数据做附加操作
*/
private String executor = "simple";
/**
* 生成器
*/
private Generate generate = new Generate();
/**
* 缓存设置
*/
private Cache cache = new Cache();
@Data
public class Generate {
/**
* 生成器定界符
*/
private String delimiter = ":";
}
@Data
public class Cache {
/**
* 缓存类型
*/
private String type = "local";
/**
* 过期时间(s) -1 表示永不过期
*/
private Long expire = -1L;
}
public boolean isNullValue(String val) {
return this.nullValue.equals(val);
}
}

View File

@@ -1,38 +0,0 @@
package com.hccake.common.i18n.cache;
/**
* 缓存服务 规范国际化的缓存
*
* @author Yakir
*/
public interface CacheService {
/**
* 设置过期时间永不过期
* @param cacheKey
* @param cacheValue
*/
void put(String cacheKey, String cacheValue);
/**
* 设置缓存
* @param cacheKey 缓存key
* @param cacheValue 值
* @param expireTime 秒(S)为单位
*/
void put(String cacheKey, String cacheValue, Long expireTime);
/**
* 根据key 得到数据
* @param cacheKey 缓存key
* @return string
*/
String get(String cacheKey);
/**
* 根据key删除
* @param cacheKey 缓存key
*/
void del(String cacheKey);
}

View File

@@ -1,38 +0,0 @@
package com.hccake.common.i18n.cache;
import java.util.HashMap;
import java.util.Map;
/**
* local cache
*
* @author Yakir
*/
public class LocalCacheService implements CacheService {
/**
* 本地缓存 map
*/
private static final Map<String, String> CACHE_MAP = new HashMap<>();
@Override
public void put(String cacheKey, String cacheValue) {
put(cacheKey, cacheValue, null);
}
@Override
public void put(String cacheKey, String cacheValue, Long expireTime) {
CACHE_MAP.put(cacheKey, cacheValue);
}
@Override
public String get(String cacheKey) {
return CACHE_MAP.get(cacheKey);
}
@Override
public void del(String cacheKey) {
CACHE_MAP.remove(cacheKey);
}
}

View File

@@ -1,43 +0,0 @@
package com.hccake.common.i18n.cache;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
* redis 缓存
*
* @author Yakir
*/
@RequiredArgsConstructor
public class RedisCacheService implements CacheService {
private final StringRedisTemplate redisTemplate;
@Override
public void put(String cacheKey, String cacheValue) {
redisTemplate.opsForValue().set(cacheKey, cacheValue);
}
@Override
public void put(String cacheKey, String cacheValue, Long expireTime) {
if (expireTime <= 0) {
this.put(cacheKey, cacheValue);
}
else {
redisTemplate.opsForValue().set(cacheKey, cacheValue, expireTime, TimeUnit.SECONDS);
}
}
@Override
public String get(String cacheKey) {
return redisTemplate.opsForValue().get(cacheKey);
}
@Override
public void del(String cacheKey) {
redisTemplate.delete(cacheKey);
}
}

View File

@@ -1,84 +0,0 @@
package com.hccake.common.i18n.execute;
import org.springframework.context.i18n.LocaleContextHolder;
import java.util.*;
/**
* abstract translate execute
*
* @author Yakir
*/
public abstract class AbstractTranslateExecute implements TranslateExecute {
@SuppressWarnings("unchecked")
@Override
public void translateObject(Object source) {
if (source == null) {
return;
}
// 获取本地语言环境
Locale locale = LocaleContextHolder.getLocale();
String language = locale.toString();
if (source instanceof List) {
processObjects((List<Object>) source, language);
}
else if (source instanceof Set) {
processObjects((Set) source, language);
}
else {
processObject(source, language);
}
}
@SuppressWarnings("unchecked")
@Override
public void translateObject(Object source, Map<String, String> params) {
if (source == null) {
return;
}
// 获取本地语言环境
Locale locale = LocaleContextHolder.getLocale();
String language = locale.toString();
if (source instanceof List) {
processObjects((List<Object>) source, language, params);
}
else if (source instanceof Set) {
processObjects((Set<Object>) source, language, params);
}
else {
processObject(source, language, params);
}
}
/**
* 处理对象 不带参数的
* @param object 处理数据对象
* @param language 语言环境
*/
public abstract void processObject(Object object, String language);
/**
* 处理对象 不带参数
* @param sources 元对象集合
* @param language 语言环境
*/
public abstract <T extends Collection> void processObjects(T sources, String language);
/**
* 处理对象带参数 一批对象公用一份参数
* @param sources 元对象集合
* @param language 语言环境
* @param params 参数
*/
public abstract <T extends Collection> void processObjects(T sources, String language, Map<String, String> params);
/**
* 处理对象单个 带参数
* @param source 元对象
* @param language 语言环境
* @param params 参数
*/
public abstract void processObject(Object source, String language, Map<String, String> params);
}

View File

@@ -1,216 +0,0 @@
package com.hccake.common.i18n.execute;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.hccake.common.i18n.annotation.I18nField;
import com.hccake.common.i18n.executor.ExecutorWrapper;
import com.hccake.common.i18n.handler.TranslateHandler;
import com.hccake.common.i18n.handler.TranslateHandlerHolder;
import com.hccake.common.i18n.model.I18nValueItem;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.i18n.LocaleContextHolder;
import java.lang.reflect.Field;
import java.util.*;
/**
* 翻译执行器
*
* @author Yakir
*/
@Slf4j
@RequiredArgsConstructor
public class DefaultTranslateExecute extends AbstractTranslateExecute {
private final ExecutorWrapper executorWrapper;
@Override
public String translateText(String businessCode, String code) {
String languageName = LocaleContextHolder.getLocale().toString();
return translateText(businessCode, code, languageName, null, code);
}
@Override
public String translateText(String businessCode, String code, String languageName) {
return translateText(businessCode, code, languageName, null, code);
}
@Override
public String translateText(String businessCode, String code, Map<String, String> params) {
String languageName = LocaleContextHolder.getLocale().toString();
return translateText(businessCode, code, languageName, params, code);
}
@Override
public String translateText(String businessCode, String code, String languageName,
Map<String, String> paramValues) {
return translateText(businessCode, code, languageName, paramValues, code);
}
public String translateText(String businessCode, String code, String languageName, Map<String, String> paramValues,
String defaultValue) {
I18nValueItem i18nValueItem = executorWrapper.selectLocaleLanguage(businessCode, code, languageName);
if (i18nValueItem == null) {
return code;
}
Integer type = i18nValueItem.getType();
String tplValue = i18nValueItem.getTplValue();
TranslateHandler translateHandler = TranslateHandlerHolder.getTranslateHandler(type);
Assert.notNull(translateHandler, "translateHandler can not be Null");
String resultValue = translateHandler.translateText(tplValue, paramValues);
return StrUtil.isNotEmpty(resultValue) ? resultValue : defaultValue;
}
@Override
public <T extends Collection> void processObjects(T sources, String language, Map<String, String> params) {
if (CollectionUtil.isEmpty(sources)) {
return;
}
for (Object source : sources) {
processObject(source, language, params);
}
}
@Override
public <T extends Collection> void processObjects(T sources, String language) {
this.processObjects(sources, language, null);
}
@Override
public void processObject(Object source, String language) {
this.processObject(source, language, null);
}
@Override
public void processObject(Object source, String language, Map<String, String> params) {
if (source == null) {
return;
}
Class<?> sourceClass = source.getClass();
// 若为基本类型 或string类中直接跳过
if (isBaseTypeOrString(sourceClass)) {
return;
}
for (Field field : sourceClass.getDeclaredFields()) {
Class<?> fieldType = field.getType();
// 若为排除类型直接跳过
if (isExcludeFieldType(fieldType)) {
continue;
}
if (List.class.isAssignableFrom(fieldType)) {
// 防止实体对象里面防止list属性
List<Object> elementsList = getFieldValue(field, source);
if (CollectionUtil.isEmpty(elementsList)) {
continue;
}
processObjects(elementsList, language);
continue;
}
else if (Set.class.isAssignableFrom(fieldType)) {
Set elementSet = getFieldValue(field, source);
if (CollectionUtil.isEmpty(elementSet)) {
continue;
}
processObjects(new ArrayList<>(elementSet), language);
continue;
}
// 若不存在国际化注解 直接跳过
if (!field.isAnnotationPresent(I18nField.class)) {
continue;
}
// 设置字段为可进入
String fieldValue = getFieldValue(field, source);
// 若字段值为空 则直接跳过
if (StrUtil.isEmpty(fieldValue)) {
continue;
}
I18nField annotation = field.getAnnotation(I18nField.class);
String[] rangeValue = annotation.rangeValue();
String defaultValue = annotation.defaultValue();
// rangeValue 不为空 并且当前元素 不在范围值内 直接跳过
if (ArrayUtil.isNotEmpty(rangeValue) && !ArrayUtil.contains(rangeValue, fieldValue)) {
continue;
}
String businessCode = annotation.businessCode();
I18nValueItem i18nValueItem = executorWrapper.selectLocaleLanguage(businessCode, fieldValue, language);
if (i18nValueItem == null) {
continue;
}
String tplValue = i18nValueItem.getTplValue();
Integer type = i18nValueItem.getType();
TranslateHandler translateHandler = TranslateHandlerHolder.getTranslateHandler(type);
if (translateHandler == null) {
continue;
}
String afterValue = translateHandler.translateText(tplValue, params);
if (StrUtil.isEmpty(afterValue)) {
if (StrUtil.isEmpty(defaultValue)) {
continue;
}
afterValue = defaultValue;
}
// 设置进字段
setFieldValue(field, source, afterValue);
}
}
/**
* 判断是否为基础类型 或String 类型
* @param sourceClass
* @return
*/
private boolean isBaseTypeOrString(Class<?> sourceClass) {
return ClassUtil.isBasicType(sourceClass) || String.class.isAssignableFrom(sourceClass);
}
private <T> T getFieldValue(Field field, Object obj) {
try {
if (!field.isAccessible()) {
field.setAccessible(true);
}
return (T) field.get(obj);
}
catch (IllegalAccessException e) {
log.error("字段值获取失败", e);
}
return null;
}
/**
* 设置字段值
* @param field 字段
* @param obj 对象
* @param val 新值
*/
private void setFieldValue(Field field, Object obj, String val) {
try {
if (!field.isAccessible()) {
field.setAccessible(true);
}
field.set(obj, val);
}
catch (IllegalAccessException e) {
log.error("国际化处理结果回填失败", e);
}
}
/**
* 是否为排除字段类型
* @param clazz string List map 保留
* @return true 排除 false 进行翻译
*/
public boolean isExcludeFieldType(Class clazz) {
if (String.class.isAssignableFrom(clazz) || List.class.isAssignableFrom(clazz)
|| Set.class.isAssignableFrom(clazz)) {
return false;
}
return true;
}
}

View File

@@ -1,56 +0,0 @@
package com.hccake.common.i18n.execute;
import java.util.Map;
public interface TranslateExecute {
/**
* 翻译文本使用默认语言环境
* @param businessCode
* @param code
* @return
*/
String translateText(String businessCode, String code);
/**
* 翻译文本 指定语言环境
* @param businessCode
* @param code
* @param language
* @return
*/
String translateText(String businessCode, String code, String language);
/**
* 翻译文本使用当前语言环境
* @param businessCode
* @param code
* @param params
* @return
*/
String translateText(String businessCode, String code, Map<String, String> params);
/**
* 翻译文本 指定语言环境
* @param businessCode
* @param code
* @param language
* @param params
* @return
*/
String translateText(String businessCode, String code, String language, Map<String, String> params);
/**
* 翻译对象 直接进行字段值更新
* @param source
*/
void translateObject(Object source);
/**
* 翻译单个对象 可以指定参数
* @param source
* @param params
*/
void translateObject(Object source, Map<String, String> params);
}

View File

@@ -1,42 +0,0 @@
package com.hccake.common.i18n.execute;
import java.util.Map;
/**
* 翻译执行包装器
*
* @author Yakir
*/
public class TranslateExecuteWrapper {
private static TranslateExecute translateExecute;
public static String translateText(String businessCode, String code) {
return translateExecute.translateText(businessCode, code);
}
public static String translateText(String businessCode, String code, String language) {
return translateExecute.translateText(businessCode, code, language);
}
public static String translateText(String businessCode, String code, Map<String, String> params) {
return translateExecute.translateText(businessCode, code, params);
}
public static String translateText(String businessCode, String code, String language, Map<String, String> params) {
return translateExecute.translateText(businessCode, code, language, params);
}
public static void translateObject(Object source) {
translateExecute.translateObject(source);
}
public static void translateObject(Object source, Map<String, String> params) {
translateExecute.translateObject(source, params);
}
public void setTranslateExecute(TranslateExecute translateExecute) {
TranslateExecuteWrapper.translateExecute = translateExecute;
}
}

View File

@@ -1,73 +0,0 @@
package com.hccake.common.i18n.executor;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.hccake.ballcat.common.util.JsonUtils;
import com.hccake.common.i18n.I18nDataProvider;
import com.hccake.common.i18n.I18nProperties;
import com.hccake.common.i18n.cache.CacheService;
import com.hccake.common.i18n.generate.KeyGenerate;
import com.hccake.common.i18n.model.I18nItem;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* 简单执行器 主要负责数据提取与缓存
*
* @author Yakir
*/
@RequiredArgsConstructor
public class CacheExecutor implements Executor {
private final I18nDataProvider i18nDataProvider;
private final I18nProperties i18nProperties;
private final KeyGenerate keyGenerate;
private final CacheService cacheService;
@Override
public I18nItem selectOne(String systemName, String businessCode, String code, String language) {
String key = keyGenerate.generateKey(systemName, businessCode, code, language);
String cacheValue = cacheService.get(key);
// 若为空值标记 则直接返回
if (i18nProperties.isNullValue(cacheValue)) {
return null;
}
if (StrUtil.isNotEmpty(cacheValue)) {
return JsonUtils.toObj(cacheValue, I18nItem.class);
}
I18nItem i18nItem = i18nDataProvider.selectOne(systemName, businessCode, code, language);
cacheService.put(key,
ObjectUtil.isNotEmpty(i18nItem) ? JsonUtils.toJson(i18nItem) : i18nProperties.getNullValue(),
i18nProperties.getCache().getExpire());
return i18nItem;
}
@Override
public List<I18nItem> selectListByCode(String systemName, String businessCode, String code) {
String key = keyGenerate.generateKey(systemName, businessCode, code);
String cacheValue = cacheService.get(key);
// 若为空值标记 则直接返回
if (i18nProperties.isNullValue(cacheValue)) {
return new ArrayList<>();
}
if (StrUtil.isNotEmpty(cacheValue)) {
return JsonUtils.toObj(cacheValue, List.class);
}
List<I18nItem> i18nItems = i18nDataProvider.selectListByCode(systemName, businessCode, code);
cacheService.put(key,
ObjectUtil.isNotEmpty(i18nItems) ? JsonUtils.toJson(i18nItems) : i18nProperties.getNullValue(),
i18nProperties.getCache().getExpire());
return i18nItems;
}
}

View File

@@ -1,33 +0,0 @@
package com.hccake.common.i18n.executor;
import com.hccake.common.i18n.model.I18nItem;
import java.util.List;
/**
* 执行器 主要负责数据提取与附加操作
*
* @author Yakir
*/
public interface Executor {
/**
* 指定业务 业务码 语言环境
* @param systemName 系统名称
* @param businessCode 业务
* @param code key
* @param language 语言环境
* @return 值
*/
I18nItem selectOne(String systemName, String businessCode, String code, String language);
/**
* 查询列表 指定code 查询出所有语言环境
* @param systemName 系统名称
* @param businessCode 业务码
* @param code key
* @return 值列表
*/
List<I18nItem> selectListByCode(String systemName, String businessCode, String code);
}

View File

@@ -1,47 +0,0 @@
package com.hccake.common.i18n.executor;
import com.hccake.common.i18n.I18nProperties;
import com.hccake.common.i18n.model.I18nItem;
import com.hccake.common.i18n.model.I18nValueItem;
import lombok.RequiredArgsConstructor;
/**
* 执行者包装类 主要负责数据的处理转换
*
* @author Yakir
*/
@RequiredArgsConstructor
public class ExecutorWrapper {
private final I18nProperties i18nProperties;
private final Executor executor;
/**
* 获取系统名
* @return
*/
private String getSystemName() {
return i18nProperties.getSystemName();
}
/**
* 查询区域项 语言
* @param businessCode
* @param code
* @param language
* @return {@link I18nValueItem} 对应处理后的国际化值
*/
public I18nValueItem selectLocaleLanguage(String businessCode, String code, String language) {
I18nItem i18nItem = executor.selectOne(getSystemName(), businessCode, code, language);
if (i18nItem == null) {
return null;
}
return convertI18nValueItem(i18nItem);
}
private I18nValueItem convertI18nValueItem(I18nItem e) {
return new I18nValueItem().setTplValue(e.getValue()).setType(e.getType());
}
}

View File

@@ -1,29 +0,0 @@
package com.hccake.common.i18n.executor;
import com.hccake.common.i18n.I18nDataProvider;
import com.hccake.common.i18n.model.I18nItem;
import lombok.RequiredArgsConstructor;
import java.util.List;
/**
* 简单执行器 主要负责数据提取与缓存
*
* @author Yakir
*/
@RequiredArgsConstructor
public class SimpleExecutor implements Executor {
private final I18nDataProvider i18nDataProvider;
@Override
public I18nItem selectOne(String systemName, String businessCode, String code, String language) {
return i18nDataProvider.selectOne(systemName, businessCode, code, language);
}
@Override
public List<I18nItem> selectListByCode(String systemName, String businessCode, String code) {
return i18nDataProvider.selectListByCode(systemName, businessCode, code);
}
}

View File

@@ -1,28 +0,0 @@
package com.hccake.common.i18n.generate;
import cn.hutool.core.lang.Assert;
import com.hccake.common.i18n.I18nProperties;
import lombok.RequiredArgsConstructor;
/**
* 默认key生成
*
* @author Yakir
*/
@RequiredArgsConstructor
public class DefaultKeyGenerate implements KeyGenerate {
private final I18nProperties i18nProperties;
@Override
public String generateKey(String... params) {
Assert.noNullElements(params, "params size must be greater than 0 ");
StringBuilder stringBuilder = new StringBuilder();
I18nProperties.Generate generate = i18nProperties.getGenerate();
for (String param : params) {
stringBuilder.append(param).append(generate.getDelimiter());
}
return stringBuilder.deleteCharAt(stringBuilder.length() - 1).toString();
}
}

View File

@@ -1,17 +0,0 @@
package com.hccake.common.i18n.generate;
/**
* 缓存key生成器
*
* @author Yakir
*/
public interface KeyGenerate {
/**
* 缓存 key 生成
* @param params 参数数组
* @return 指定分隔符字符串
*/
String generateKey(String... params);
}

View File

@@ -1,20 +0,0 @@
package com.hccake.common.i18n.handler;
import lombok.RequiredArgsConstructor;
import java.util.Map;
/**
* simple translate handler
*
* @author Yakir
*/
@RequiredArgsConstructor
public class SimpleTranslateHandler implements TranslateHandler {
@Override
public String translateText(String originalText, Map<String, String> paramValues) {
return originalText;
}
}

View File

@@ -1,21 +0,0 @@
package com.hccake.common.i18n.handler;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import java.util.Map;
/**
* template translate handler
*
* @author Yakir
*/
@RequiredArgsConstructor
public class TemplateTranslateHandler implements TranslateHandler {
@Override
public String translateText(String originalText, Map<String, String> paramValues) {
return StrUtil.format(originalText, paramValues);
}
}

View File

@@ -1,20 +0,0 @@
package com.hccake.common.i18n.handler;
import java.util.Map;
/**
* 翻译处理器 具体执行处理逻辑
*
* @author Yakir
*/
public interface TranslateHandler {
/**
* 翻译文本
* @param originalText 原始文本
* @param paramValues 参数值
* @return 翻译后的值
*/
String translateText(String originalText, Map<String, String> paramValues);
}

View File

@@ -1,77 +0,0 @@
package com.hccake.common.i18n.handler;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/**
* translate handler holder
*
* @author Yakir
*/
@RequiredArgsConstructor
public class TranslateHandlerHolder {
/**
* 处理器map元素
*/
private static final Map<Class<? extends TranslateHandler>, TranslateHandler> HANDLER_MAP = new HashMap<>();
/**
* 枚举类型type标记 与类型的映射
*/
private static final Map<Integer, Class<? extends TranslateHandler>> TYPE_CLASS_MAP = new HashMap<>();
static {
TYPE_CLASS_MAP.put(1, SimpleTranslateHandler.class);
TYPE_CLASS_MAP.put(2, TemplateTranslateHandler.class);
HANDLER_MAP.put(SimpleTranslateHandler.class, new SimpleTranslateHandler());
HANDLER_MAP.put(TemplateTranslateHandler.class, new TemplateTranslateHandler());
}
/**
* 得到处理器
* @param clazz TranslateHandler的Class
* @return @{code TranslateHandler实现}
*/
public static TranslateHandler getTranslateHandler(Class<? extends TranslateHandler> clazz) {
return HANDLER_MAP.get(clazz);
}
/**
* 添加处理器
* @param clazz TranslateHandler的Class
* @param translateHandler TranslateHandler实现
* @return @{code TranslateHandler实现}
*/
public static TranslateHandler addTranslateHandler(Class<? extends TranslateHandler> clazz,
TranslateHandler translateHandler) {
return HANDLER_MAP.put(clazz, translateHandler);
}
/**
* 得到处理器 主要拓展 绑定type 与类的映射
* @param type type
* @return @{code TranslateHandler实现}
*/
public static TranslateHandler getTranslateHandler(Integer type) {
return HANDLER_MAP.get(TYPE_CLASS_MAP.get(type));
}
/**
* 添加处理器 主要拓展 绑定type 与类的映射
* @param type 类型1.明文 2.模板
* @param clazz TranslateHandler的Class
* @param translateHandler TranslateHandler实现
* @return @{code TranslateHandler实现}
*/
public static TranslateHandler addTranslateHandler(Integer type, Class<? extends TranslateHandler> clazz,
TranslateHandler translateHandler) {
TYPE_CLASS_MAP.put(type, clazz);
return HANDLER_MAP.put(clazz, translateHandler);
}
}

View File

@@ -1,45 +0,0 @@
package com.hccake.common.i18n.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* i18n item
*
* @author Yakir
*/
@Accessors(chain = true)
@Data
public class I18nItem {
/**
* 系统名称
*/
private String systemName;
/**
* 业务码
*/
private String businessCode;
/**
* 分组code
*/
private String code;
/**
* 语言环境
*/
private String language;
/**
* 值
*/
private String value;
/**
* 类型
*/
private Integer type;
}

View File

@@ -1,25 +0,0 @@
package com.hccake.common.i18n.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* i18n
*
* @author Yakir
*/
@Data
@Accessors(chain = true)
public class I18nValueItem {
/**
* 模板值
*/
private String tplValue;
/**
* 类型 1.明文 2.模板
*/
private Integer type;
}

View File

@@ -1,18 +0,0 @@
<?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">
<parent>
<artifactId>ballcat-extends</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-extend-i18n</artifactId>
<packaging>pom</packaging>
<modules>
<module>ballcat-extend-i18n-annotation</module>
<module>ballcat-extend-i18n-core</module>
</modules>
</project>

View File

@@ -19,7 +19,6 @@
<module>ballcat-extend-pay-virtual</module>
<module>ballcat-extend-pay-ali</module>
<module>ballcat-extend-pay-wx</module>
<module>ballcat-extend-i18n</module>
<module>ballcat-extend-redis-module</module>
</modules>
</project>

View File

@@ -0,0 +1,25 @@
<?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">
<parent>
<artifactId>ballcat-i18n</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-i18n-biz</artifactId>
<dependencies>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-i18n-model</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-extend-mybatis-plus</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,49 @@
package com.hccake.ballcat.i18n.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hccake.ballcat.i18n.converter.I18nDataConverter;
import com.hccake.ballcat.i18n.model.entity.I18nData;
import com.hccake.ballcat.i18n.model.qo.I18nDataQO;
import com.hccake.ballcat.i18n.model.vo.I18nDataPageVO;
import com.hccake.ballcat.common.model.domain.PageParam;
import com.hccake.ballcat.common.model.domain.PageResult;
import com.hccake.extend.mybatis.plus.conditions.query.LambdaQueryWrapperX;
import com.hccake.extend.mybatis.plus.mapper.ExtendMapper;
import com.hccake.extend.mybatis.plus.toolkit.WrappersX;
/**
* 国际化信息
*
* @author hccake 2021-08-04 11:31:49
*/
public interface I18nDataMapper extends ExtendMapper<I18nData> {
/**
* 分页查询
* @param pageParam 分页参数
* @param qo 查询参数
* @return PageResult<I18nDataPageVO> VO分页数据
*/
default PageResult<I18nDataPageVO> queryPage(PageParam pageParam, I18nDataQO qo) {
IPage<I18nData> page = this.prodPage(pageParam);
LambdaQueryWrapperX<I18nData> wrapper = WrappersX.lambdaQueryX(I18nData.class);
this.selectPage(page, wrapper);
IPage<I18nDataPageVO> voPage = page.convert(I18nDataConverter.INSTANCE::poToPageVo);
return new PageResult<>(voPage.getRecords(), voPage.getTotal());
}
/**
* 根据 code 和 languageTag 查询指定的 I18nData
* @param code 唯一标识
* @param languageTag 语言标识
* @return I18nData
*/
default I18nData selectByCodeAndLanguageTag(String code, String languageTag) {
LambdaQueryWrapper<I18nData> wrapper = Wrappers.lambdaQuery(I18nData.class).eq(I18nData::getCode, code)
.eq(I18nData::getLanguageTag, languageTag);
return this.selectOne(wrapper);
}
}

View File

@@ -0,0 +1,35 @@
package com.hccake.ballcat.i18n.provider;
import com.hccake.ballcat.common.i18n.I18nMessage;
import com.hccake.ballcat.common.i18n.I18nMessageProvider;
import com.hccake.ballcat.i18n.model.entity.I18nData;
import com.hccake.ballcat.i18n.service.I18nDataService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Locale;
/**
* @author hccake
*/
@Component
@RequiredArgsConstructor
public class CustomI18nMessageProvider implements I18nMessageProvider {
private final I18nDataService i18nDataService;
@Override
public I18nMessage getI18nMessage(String code, Locale locale) {
String languageTag = locale.toLanguageTag();
I18nData i18nData = i18nDataService.getByCodeAndLanguageTag(code, languageTag);
if (i18nData == null) {
return null;
}
I18nMessage i18nMessage = new I18nMessage();
i18nMessage.setMessage(i18nData.getMessage());
i18nMessage.setCode(i18nData.getCode());
i18nMessage.setLanguageTag(i18nData.getCode());
return i18nMessage;
}
}

View File

@@ -0,0 +1,33 @@
package com.hccake.ballcat.i18n.service;
import com.hccake.ballcat.i18n.model.entity.I18nData;
import com.hccake.ballcat.i18n.model.vo.I18nDataPageVO;
import com.hccake.ballcat.i18n.model.qo.I18nDataQO;
import com.hccake.ballcat.common.model.domain.PageParam;
import com.hccake.ballcat.common.model.domain.PageResult;
import com.hccake.extend.mybatis.plus.service.ExtendService;
/**
* 国际化信息
*
* @author hccake 2021-08-04 11:31:49
*/
public interface I18nDataService extends ExtendService<I18nData> {
/**
* 根据QueryObeject查询分页数据
* @param pageParam 分页参数
* @param qo 查询参数对象
* @return PageResult&lt;I18nDataPageVO&gt; 分页数据
*/
PageResult<I18nDataPageVO> queryPage(PageParam pageParam, I18nDataQO qo);
/**
* 根据 code 和 languageTag 查询指定的 I18nData
* @param code 唯一标识
* @param languageTag 语言标识
* @return I18nData
*/
I18nData getByCodeAndLanguageTag(String code, String languageTag);
}

View File

@@ -0,0 +1,43 @@
package com.hccake.ballcat.i18n.service.impl;
import com.hccake.ballcat.i18n.model.entity.I18nData;
import com.hccake.ballcat.i18n.model.vo.I18nDataPageVO;
import com.hccake.ballcat.i18n.model.qo.I18nDataQO;
import com.hccake.ballcat.i18n.mapper.I18nDataMapper;
import com.hccake.ballcat.i18n.service.I18nDataService;
import com.hccake.ballcat.common.model.domain.PageParam;
import com.hccake.ballcat.common.model.domain.PageResult;
import com.hccake.extend.mybatis.plus.service.impl.ExtendServiceImpl;
import org.springframework.stereotype.Service;
/**
* 国际化信息
*
* @author hccake 2021-08-04 11:31:49
*/
@Service
public class I18nDataServiceImpl extends ExtendServiceImpl<I18nDataMapper, I18nData> implements I18nDataService {
/**
* 根据QueryObeject查询分页数据
* @param pageParam 分页参数
* @param qo 查询参数对象
* @return PageResult<I18nDataPageVO> 分页数据
*/
@Override
public PageResult<I18nDataPageVO> queryPage(PageParam pageParam, I18nDataQO qo) {
return baseMapper.queryPage(pageParam, qo);
}
/**
* 根据 code 和 languageTag 查询指定的 I18nData
* @param code 唯一标识
* @param languageTag 语言标识
* @return I18nData
*/
@Override
public I18nData getByCodeAndLanguageTag(String code, String languageTag) {
return baseMapper.selectByCodeAndLanguageTag(code, languageTag);
}
}

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hccake.ballcat.i18n.mapper.I18nDataMapper">
<resultMap id="i18nDataMap" type="com.hccake.ballcat.i18n.model.entity.I18nData">
<id property="id" column="id"/>
<result property="business" column="business"/>
<result property="messageKey" column="message_key"/>
<result property="code" column="code"/>
<result property="message" column="message"/>
<result property="languageTag" column="language_tag"/>
<result property="remark" column="remark"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="Base_Column_List">
id,
business,
message_key,
code,
message,
language_tag,
remark,
create_time,
update_time
</sql>
<sql id="Base_Alias_Column_List">
id.id,
id.business,
id.message_key,
id.code,
id.message,
id.language_tag,
id.remark,
id.create_time,
id.update_time
</sql>
</mapper>

View File

@@ -3,28 +3,31 @@
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">
<parent>
<artifactId>ballcat-extend-i18n</artifactId>
<artifactId>ballcat-i18n</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-extend-i18n-core</artifactId>
<packaging>jar</packaging>
<artifactId>ballcat-i18n-controller</artifactId>
<dependencies>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-extend-i18n-annotation</artifactId>
<artifactId>ballcat-i18n-biz</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-core</artifactId>
<artifactId>ballcat-common-log</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-spring-boot-starter-redis</artifactId>
<artifactId>ballcat-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,87 @@
package com.hccake.ballcat.i18n.controller;
import com.hccake.ballcat.common.log.operation.annotation.CreateOperationLogging;
import com.hccake.ballcat.common.log.operation.annotation.DeleteOperationLogging;
import com.hccake.ballcat.common.log.operation.annotation.UpdateOperationLogging;
import com.hccake.ballcat.common.model.domain.PageParam;
import com.hccake.ballcat.common.model.domain.PageResult;
import com.hccake.ballcat.common.model.result.BaseResultCode;
import com.hccake.ballcat.common.model.result.R;
import com.hccake.ballcat.i18n.model.entity.I18nData;
import com.hccake.ballcat.i18n.model.qo.I18nDataQO;
import com.hccake.ballcat.i18n.model.vo.I18nDataPageVO;
import com.hccake.ballcat.i18n.service.I18nDataService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* 国际化信息
*
* @author hccake 2021-08-04 11:31:49
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/i18n/data")
@Api(value = "i18n-data", tags = "国际化信息管理")
public class I18nDataController {
private final I18nDataService i18nDataService;
/**
* 分页查询
* @param pageParam 分页参数
* @param i18nDataQO 国际化信息查询对象
* @return R 通用返回体
*/
@ApiOperation(value = "分页查询", notes = "分页查询")
@GetMapping("/page")
@PreAuthorize("@per.hasPermission('i18n:data:read')")
public R<PageResult<I18nDataPageVO>> getI18nDataPage(PageParam pageParam, I18nDataQO i18nDataQO) {
return R.ok(i18nDataService.queryPage(pageParam, i18nDataQO));
}
/**
* 新增国际化信息
* @param i18nData 国际化信息
* @return R 通用返回体
*/
@ApiOperation(value = "新增国际化信息", notes = "新增国际化信息")
@CreateOperationLogging(msg = "新增国际化信息")
@PostMapping
@PreAuthorize("@per.hasPermission('i18n:data:add')")
public R save(@RequestBody I18nData i18nData) {
return i18nDataService.save(i18nData) ? R.ok() : R.failed(BaseResultCode.UPDATE_DATABASE_ERROR, "新增国际化信息失败");
}
/**
* 修改国际化信息
* @param i18nData 国际化信息
* @return R 通用返回体
*/
@ApiOperation(value = "修改国际化信息", notes = "修改国际化信息")
@UpdateOperationLogging(msg = "修改国际化信息")
@PutMapping
@PreAuthorize("@per.hasPermission('i18n:data:edit')")
public R updateById(@RequestBody I18nData i18nData) {
return i18nDataService.updateById(i18nData) ? R.ok()
: R.failed(BaseResultCode.UPDATE_DATABASE_ERROR, "修改国际化信息失败");
}
/**
* 通过id删除国际化信息
* @param id id
* @return R 通用返回体
*/
@ApiOperation(value = "通过id删除国际化信息", notes = "通过id删除国际化信息")
@DeleteOperationLogging(msg = "通过id删除国际化信息")
@DeleteMapping("/{id}")
@PreAuthorize("@per.hasPermission('i18n:data:del')")
public R removeById(@PathVariable("id") Integer id) {
return i18nDataService.removeById(id) ? R.ok()
: R.failed(BaseResultCode.UPDATE_DATABASE_ERROR, "通过id删除国际化信息失败");
}
}

View File

@@ -0,0 +1,25 @@
<?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">
<parent>
<artifactId>ballcat-i18n</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-i18n-model</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,25 @@
package com.hccake.ballcat.i18n.converter;
import com.hccake.ballcat.i18n.model.entity.I18nData;
import com.hccake.ballcat.i18n.model.vo.I18nDataPageVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 国际化信息模型转换器
*
* @author hccake 2021-08-04 11:31:49
*/
@Mapper
public interface I18nDataConverter {
I18nDataConverter INSTANCE = Mappers.getMapper(I18nDataConverter.class);
/**
* PO 转 PageVO
* @param i18nData 国际化信息
* @return I18nDataPageVO 国际化信息PageVO
*/
I18nDataPageVO poToPageVo(I18nData i18nData);
}

View File

@@ -0,0 +1,82 @@
package com.hccake.ballcat.i18n.model.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 国际化信息
*
* @author hccake 2021-08-04 11:31:49
*/
@Data
@TableName("i18n_data")
@ApiModel(value = "国际化信息")
public class I18nData {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@TableId
@ApiModelProperty(value = "ID")
private Integer id;
/**
* 业务
*/
@ApiModelProperty(value = "业务")
private String business;
/**
* 关键词
*/
@ApiModelProperty(value = "关键词")
private String messageKey;
/**
* 唯一标识 = 业务:关键词
*/
@ApiModelProperty(value = "唯一标识 = 业务:关键词")
private String code;
/**
* 文本值,可以使用 { } 加角标,作为占位符
*/
@ApiModelProperty(value = "文本值,可以使用 { } 加角标,作为占位符")
private String message;
/**
* 语言标识
*/
@ApiModelProperty(value = "语言标识")
private String languageTag;
/**
* 备注
*/
@ApiModelProperty(value = "备注")
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@ApiModelProperty(value = "修改时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,24 @@
package com.hccake.ballcat.i18n.model.qo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 国际化信息 查询对象
*
* @author hccake 2021-08-04 11:31:49
*/
@Data
@ApiModel(value = "国际化信息查询对象")
public class I18nDataQO {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ApiModelProperty(value = "ID")
private Integer id;
}

View File

@@ -0,0 +1,74 @@
package com.hccake.ballcat.i18n.model.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 国际化信息分页视图对象
*
* @author hccake 2021-08-04 11:31:49
*/
@Data
@ApiModel(value = "国际化信息分页视图对象")
public class I18nDataPageVO {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ApiModelProperty(value = "ID")
private Integer id;
/**
* 业务
*/
@ApiModelProperty(value = "业务")
private String business;
/**
* 关键词
*/
@ApiModelProperty(value = "关键词")
private String messageKey;
/**
* 唯一标识 = 业务:关键词
*/
@ApiModelProperty(value = "唯一标识 = 业务:关键词")
private String code;
/**
* 文本值,可以使用 { } 加角标,作为占位符
*/
@ApiModelProperty(value = "文本值,可以使用 { } 加角标,作为占位符")
private String message;
/**
* 语言标识
*/
@ApiModelProperty(value = "语言标识")
private String languageTag;
/**
* 备注
*/
@ApiModelProperty(value = "备注")
private String remark;
/**
* 创建时间
*/
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 修改时间
*/
@ApiModelProperty(value = "修改时间")
private LocalDateTime updateTime;
}

View File

@@ -3,16 +3,18 @@
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">
<parent>
<artifactId>ballcat-extend-i18n</artifactId>
<artifactId>ballcat</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-extend-i18n-annotation</artifactId>
<packaging>jar</packaging>
<artifactId>ballcat-i18n</artifactId>
<packaging>pom</packaging>
<modules>
<module>ballcat-i18n-model</module>
<module>ballcat-i18n-biz</module>
<module>ballcat-i18n-controller</module>
</modules>
<dependencies>
</dependencies>
</project>

View File

@@ -1,32 +0,0 @@
<?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>
<artifactId>ballcat-starters</artifactId>
<groupId>com.hccake</groupId>
<version>${revision}</version>
</parent>
<artifactId>ballcat-spring-boot-starter-i18n</artifactId>
<dependencies>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-extend-i18n-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,89 +0,0 @@
package com.hccake.common.i18n;
import com.hccake.common.i18n.cache.CacheService;
import com.hccake.common.i18n.cache.LocalCacheService;
import com.hccake.common.i18n.cache.RedisCacheService;
import com.hccake.common.i18n.execute.DefaultTranslateExecute;
import com.hccake.common.i18n.execute.TranslateExecute;
import com.hccake.common.i18n.execute.TranslateExecuteWrapper;
import com.hccake.common.i18n.executor.CacheExecutor;
import com.hccake.common.i18n.executor.Executor;
import com.hccake.common.i18n.executor.ExecutorWrapper;
import com.hccake.common.i18n.executor.SimpleExecutor;
import com.hccake.common.i18n.generate.DefaultKeyGenerate;
import com.hccake.common.i18n.generate.KeyGenerate;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* i18n auto configuration
*
* @author Yakir
*/
@RequiredArgsConstructor
@EnableConfigurationProperties(I18nProperties.class)
public class I18nAutoConfiguration {
private final I18nProperties i18nProperties;
private final I18nDataProvider i18nDataProvider;
@Bean
@ConditionalOnMissingBean
public KeyGenerate keyGenerate() {
return new DefaultKeyGenerate(i18nProperties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "ballcat.i18n.cache", name = "type", havingValue = "local", matchIfMissing = true)
public CacheService localCacheService() {
return new LocalCacheService();
}
@Bean
@ConditionalOnMissingBean
@DependsOn("redisTemplate")
@ConditionalOnProperty(prefix = "ballcat.i18n.cache", name = "type", havingValue = "redis")
public CacheService redisCacheService(StringRedisTemplate stringRedisTemplate) {
return new RedisCacheService(stringRedisTemplate);
}
@Bean
@ConditionalOnMissingBean(Executor.class)
@ConditionalOnProperty(prefix = "ballcat.i18n", name = "executor", havingValue = "simple", matchIfMissing = true)
public Executor simpleExecutor() {
return new SimpleExecutor(i18nDataProvider);
}
@Bean
@ConditionalOnBean(CacheService.class)
@ConditionalOnMissingBean(Executor.class)
@ConditionalOnProperty(prefix = "ballcat.i18n", name = "executor", havingValue = "cache")
public Executor cacheExecutor(KeyGenerate keyGenerate, CacheService cacheService) {
return new CacheExecutor(i18nDataProvider, i18nProperties, keyGenerate, cacheService);
}
@Bean
@ConditionalOnBean(Executor.class)
@ConditionalOnMissingBean
public TranslateExecute translateExecute(Executor executor) {
return new DefaultTranslateExecute(new ExecutorWrapper(i18nProperties, executor));
}
@Bean
@ConditionalOnBean({ Executor.class, TranslateExecute.class })
@ConditionalOnMissingBean
public TranslateExecuteWrapper translateExecuteWrapper(TranslateExecute translateExecute) {
TranslateExecuteWrapper translateExecuteWrapper = new TranslateExecuteWrapper();
translateExecuteWrapper.setTranslateExecute(translateExecute);
return translateExecuteWrapper;
}
}

View File

@@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hccake.common.i18n.I18nAutoConfiguration

View File

@@ -28,7 +28,6 @@
<module>ballcat-spring-boot-starter-websocket</module>
<module>ballcat-spring-boot-starter-pay</module>
<module>ballcat-spring-boot-starter-xss</module>
<module>ballcat-spring-boot-starter-i18n</module>
</modules>
</project>

View File

@@ -11,6 +11,10 @@
<artifactId>ballcat-system-model</artifactId>
<dependencies>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-i18n</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-desensitize</artifactId>

View File

@@ -1,5 +1,7 @@
package com.hccake.ballcat.system.model.vo;
import com.hccake.ballcat.common.i18n.I18nClass;
import com.hccake.ballcat.common.i18n.I18nField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -11,6 +13,7 @@ import java.time.LocalDateTime;
*
* @author hccake 2021-04-06 17:59:51
*/
@I18nClass
@Data
@ApiModel(value = "菜单权限分页视图对象")
public class SysMenuPageVO {
@@ -32,6 +35,8 @@ public class SysMenuPageVO {
/**
* 菜单名称
*/
// TODO 原始 title 也返回,方便前端回显和修改
@I18nField
@ApiModelProperty(value = "菜单名称")
private String title;

View File

@@ -1,5 +1,7 @@
package com.hccake.ballcat.system.model.vo;
import com.hccake.ballcat.common.i18n.I18nClass;
import com.hccake.ballcat.common.i18n.I18nField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -10,6 +12,7 @@ import lombok.Data;
* @author hccake 2021-04-06 17:59:51
*/
@Data
@I18nClass
@ApiModel(value = "菜单权限视图对象")
public class SysMenuRouterVO {
@@ -30,6 +33,7 @@ public class SysMenuRouterVO {
/**
* 菜单名称
*/
@I18nField
@ApiModelProperty(value = "菜单名称")
private String title;

View File

@@ -16,7 +16,8 @@
<module>ballcat-auth</module>
<module>ballcat-notify</module>
<module>ballcat-log</module>
</modules>
<module>ballcat-i18n</module>
</modules>
<name>ballcat</name>
<description>项目基本脚手架</description>
<url>https://github.com/hccake/ballcat</url>