Java 后端国际化设计方案

前言

代码就不放全了,还在公司上跑着呢,就放一点非核心代码,工具类封装之类的

设计需求

  • 国际化配置集中到数据库中进行管理,包含前端部分国际化
  • 最好可动态添加国际化的语种
  • 好用易用
  • 高效

设计思路

  • 利用自定义注解来启用国际化,拦截所有返回请求进行处理
  • 大数据量处理使用多线程并行处理
  • 国际化数据保存在 Redis 中视为热点数据
  • 使用手动刷新方式,保证无缝刷新缓存
  • 国际化部分数据以 Json 形式来保存,保证扩展性
  • 语种以配置的形式保存,必要可添加语种
  • 需要多语言切换的数据全部以占位符代替,通过自定义注解统一替换
  • 当前语言环境通过前端带在请求头里给后端,后端默认为英文

数据库设计

  • type: 类型,非空
  • module: 模块,可为空
  • label: 标签,非空
  • langs: 国际化 Json String,非空
  • to_web: 是否返回前端,不返回的就只是后端使用,将数据切成两半

其中 type.module.label 的组合为唯一标识
在这里插入图片描述

需要国际化翻译的数据保存形式
由于后端部分为自动生成的,因此 label 使用 UUID()
在这里插入图片描述

后端返回数据部分,比如:异常提示这部分的翻译
在这里插入图片描述

功能设计

用到的工具类

JsonUtils.java

自定义注解

个人认为只需要一个启动开关即可,没必要做成那种一个个接口去加

/**
 * 开启国际化注解
 */
@Import(TranslationAspect.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableTranslation {
}

使用
在这里插入图片描述

切面开发

TranslationAspect

/**
 * 国际化实现
 * @Author: linjinp
 * @Date: 2021/1/18 15:47
 */
@Slf4j
@Aspect
public class TranslationAspect {

    // 默认语种
    private final static String DEFAULT_LANGUAGE = "en";

    // 切点
    // 指定拦截的包路径
    @Pointcut("execution(* com.xxx..*.*(..))")
    private void pointcut() {}

    @Around("pointcut()")
    private Object around(ProceedingJoinPoint pjp) throws Throwable {
		// ...
		// TODO
		// ...
		return ...
	}

从请求头获取当前语言环境

在这里插入图片描述

获取当前返回值的类型

在这里插入图片描述

/**
 * 获取列表中数据类型
 * @param obj
 * @return
 */
private static Class getArrayListClass(Object obj) {
    // 判断空,判断类型是否为列表,判断是否有数据(无数据无法获取类型)
    if (obj != null && ArrayList.class.equals(obj.getClass()) && ((List) obj).size() > 0) {
        return ((List) obj).get(0).getClass();
    }
    return null;
}

/**
 * 获取数据类型
 * @param obj
 * @return
 */
private static Class getClass(Object obj) {
    // 判断空,判断类型是否为列表,判断是否有数据(无数据无法获取类型)
    if (obj != null && !ArrayList.class.equals(obj.getClass())) {
        return obj.getClass();
    }
    return null;
}

将返回值转为 Json String 后,统一获取其中的占位符

使用 StringBuilder 保存,防止大量的对象被创建
在这里插入图片描述

/**
* 获取字符串中所有的变量参数
 * @param str 字符串/对象Json
 * @return
 */
private static List<String> findParams(String str) {
    List<String> params = new ArrayList<>();
    // 转化为二进制
    char[] chars = str.toCharArray();
    // 找到标志的索引
    int findIndex = -1;
    for (int i = 0; i < chars.length; i ++) {
        // 判断 ${ 组合
        // i <= chars.length - 3 防越界,${A,假如以此结尾,$ 在 length - 3 位置
        if (i <= chars.length - 3 && chars[i] == '$' && chars[i + 1] == '{') {
            // 获取首个变量的下标索引
            findIndex = i + 2;
        }
        // 判断 } 且,已经存在索引下标,防止前面单独出现 } 的情况
        if (chars[i] == '}' && findIndex != -1) {
            // 添加变量
            params.add(new String(Arrays.copyOfRange(chars, findIndex, i)));
            // 重置标识
            findIndex = -1;
        }
    }
    return params;
}

替换返回值中所有的占位符为对应语言

/**
 * 数据处理
 * @param lang 语言环境
 * @param data 返回数据
 * @param languages 语言包
 * @param params 需要替换的参数列表
 * @return
 */
private static StringBuilder dataProcess(String lang, StringBuilder data, List<MultiLanguage> languages, List<String> params) {
    // 循环数据
    for (MultiLanguage language : languages) {
        // 有配置语言,非空对象,为后端使用的标签
        if (StringUtils.isNotBlank(language.getLangs()) && !"{}".equals(language.getLangs())) {
            for (String param : params) {
                // 如果标签组合匹配
                if (language.equalsCombination(param)) {
                    // 假如当前环境非默认语种,判断当前语种是否已经配置,如果没配置或为空,使用默认语种数据
                    if (!DEFAULT_LANGUAGE.equals(lang) && JsonUtils.toMap(language.getLangs()).containsKey(lang) && StringUtils.isNotBlank((String) JsonUtils.toMap(language.getLangs()).get(lang))) {
                        data.replace(0, data.length(), replaceRegex(data.toString(), param, StrUtil.nullToEmpty((String) JsonUtils.toMap(language.getLangs()).get(lang))));
                    } else {
                        data.replace(0, data.length(), replaceRegex(data.toString(), param, StrUtil.nullToEmpty((String) JsonUtils.toMap(language.getLangs()).get(DEFAULT_LANGUAGE))));
                    }
                }
            }
        }
    }
    return data;
}

/**
 * 正则内容替换
 * @param source 数据
 * @param key 国际化标签
 * @param value 国际化对应值
 * @return
 */
private static String replaceRegex(String source, String key, String value) {
    String regex = "\\$\\{"+key+"\\}";
    return source.replaceAll(regex, value);
}

最后要保证返回值的类型正确

也是为了保证旧代码的兼容,比如你后来才加的国际化,这也是之前获取数据类型的原因
在这里插入图片描述

数据缓存

构建线程池

<!-- hutool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.10</version>
</dependency>
/**
 * 线程池配置
 *
 * @Author: linjinp
 * @Date: 2020/9/29 10:54
 */
@Slf4j
@Component
public class ExecutorConfig {

    public static ExecutorService executor;

    // 初始线程数量
    private final static int DEFAULT_NUM = 5;

    // 最大线程数
    private final static int MAX_NUM = 10;

    // 最大等待线程数
    private final static int MAX_WAITING = 100;

    @Bean
    public ExecutorService createExecutor() {
        this.executor = ExecutorBuilder.create()
                // 默认初始化 5 个线程
                .setCorePoolSize(DEFAULT_NUM)
                // 最大线程数 10
                .setMaxPoolSize(MAX_NUM)
                // 最大等待线程数 100
                .setWorkQueue(new LinkedBlockingQueue<>(MAX_WAITING))
                .build();
        log.info("\n初始化线程池\n默认初始线程数:{}\n最大线程数:{}\n最大等待线程数:{}", DEFAULT_NUM, MAX_NUM, MAX_WAITING);
        return this.executor;
    }
}

数据缓存到 Redis

刷新时将数据保存到 Redis 中

这里使用 多线程 + 闭锁 的方式同步进行两方的处理,闭锁保证两个线程都完成后才继续执行,返回前端成功

/**
 * 刷新国际化配置缓存
 * @return
 */
@ApiOperation("刷新国际化配置缓存")
@GetMapping(value = "/refresh")
public ErrorMsg<Map<String, Object>> refresh() throws InterruptedException {
    // 利用闭锁保证两个线程都执行完毕后返回
    final CountDownLatch countDownLatch = new CountDownLatch(2);

    // 前端国际化数据,多线程处理
    ExecutorConfig.executor.execute(new Runnable(() -> {
        try {
        	// 构建前端所需的数据格式
            Map<String, Object> languageMap = buildLangToWeb();
            // 保存前端国际化部分数据 Map
            redisTemplate.opsForValue().set(RedisKeyConfig.LANGUAGE_ZONE, languageMap);
        } finally {
            countDownLatch.countDown();
        }
    }));

    // 后端国际化数据,多线程处理
    ExecutorConfig.executor.execute(new Runnable(() -> {
        try {
        	// 获取后端所需的数据列表
            List<MultiLanguage> languageList = buildLangToJava();
            // 保存后端国际化部分数据 List
            redisTemplate.opsForValue().set(RedisKeyConfig.LANGUAGE_JAVA, languageList);
        } finally {
            countDownLatch.countDown();
        }
    }));
    // 闭锁阻塞
    countDownLatch.await();
    return ErrorMsg.SUCCESS;
}

项目启动初始化国际化数据

// 开启语言翻译
@EnableTranslation
public class AdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminApplication.class, args);
    }

    @Autowired
    private MultiLanguageController multiLanguageController;

    @Bean
    public CommandLineRunner runner() {
        return args -> {
            log.info("开始初始化国际化数据:{}", new Date());
            multiLanguageController.refresh();
            log.info("国际化初始化完成:{}", new Date());
        };
    }
}

效果展示

中文返回
在这里插入图片描述
在这里插入图片描述
英文返回
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
随着Java技术的广泛应用,越来越多的计算机相关专业学生选择Java作为毕业设计的编程语言。为了帮助大家更好地完成毕业设计,我们特地整理了一系列Java毕业设计项目参考资源,包括源代码、MD文档、笔记等等,希望能对您的学习与研究提供有力支持。 项目源代码:涵盖了多个Java毕业设计项目的完整代码,包括登录注册、用户管理、数据增删改查等功能模块的实现。这些代码均经过严格测试,可直接运行,方便您快速了解项目结构和实现细节。 MD文档:详细介绍了每个项目的需求分析、系统设计、系统实现和测试等环节,让您能够全面了解项目的开发流程和关键技术。此外,还附带了详细的API文档,方便您查阅各个功能模块的接口和参数说明。 笔记资料:整理了Java毕业设计中常见的问题和解决方案,包括数据库设计、界面美化、性能优化等方面的技巧。这些笔记资料均由经验丰富的程序员撰写,可为您提供宝贵的经验分享和指导。 视频教程:为了帮助您更好地学习和理解Java毕业设计的实现过程,我们还提供了多个视频教程,由专业讲师详细讲解各个项目的开发过程和技术要点。通过观看这些视频教程,您可以更加深入地掌握Java编程和项目开发的技能。 总之,本资源包内容丰富、实用性强,是您完成Java毕业设计的必备宝典。无论您是即将毕业的本科生还是研究生,都得拥有这份宝贵的参考资料。在此,我们诚挚地邀请您加入 平台,与众多技术同行一起交流学习,共同进步!随着Java技术的广泛应用,越来越多的计算机相关专业学生选择Java作为毕业设计的编程语言。为了帮助大家更好地完成毕业设计,我们特地整理了一系列Java毕业设计项目参考资源,包括源代码、MD文档、笔记等等,希望能对您的学习与研究提供有力支持。 项目源代码:涵盖了多个Java毕业设计项目的完整代码,包括登录注册、用户管理、数据增删改查等功能模块的实现。这些代码均经过严格测试,可直接运行,方便您快速了解项目结构和实现细节。 MD文档:详细介绍了每个项目的需求分析、系统设计、系统实现和测试等环节,让您能够全面了解项目的开发流程和关键技术。此外,还附带了详细的API文档,方便您查阅各个功能模块的接口和参数说明。 笔记资料:整理了Java毕业设计中常见的问题和解决方案,包括数据库设计、界面美化、性能优化等方面的技巧。这些笔记资料均由经验丰富的程序员撰写,可为您提供宝贵的经验分享和指导。 视频教程:为了帮助您更好地学习和理解Java毕业设计的实现过程,我们还提供了多个视频教程,由专业讲师详细讲解各个项目的开发过程和技术要点。通过观看这些视频教程,您可以更加深入地掌握Java编程和项目开发的技能。 总之,本资源包内容丰富、实用性强,是您完成Java毕业设计的必备宝典。无论您是即将毕业的本科生还是研究生,都得拥有这份宝贵的参考资料。在此,我们诚挚地邀请您加入 平台,与众多技术同行一起交流学习,共同进步!随着Java技术的广泛应用,越来越多的计算机相关专业学生选择Java作为毕业设计的编程语言。为了帮助大家更好地完成毕业设计,我们特地整理了一系列Java毕业设计项目参考资源,包括源代码、MD文档、笔记等等,希望能对您的学习与研究提供有力支持。 项目源代码:涵盖了多个Java毕业设计项目的完整代码,包括登录注册、用户管理、数据增删改查等功能模块的实现。这些代码均经过严格测试,可直接运行,方便您快速了解项目结构和实现细节。 MD文档:详细介绍了每个项目的需求分析、系统设计、系统实现和测试等环节,让您能够全面了解项目的开发流程和关键技术。此外,还附带了详细的API文档,方便您查阅各个功能模块的接口和参数说明。 笔记资料:整理了Java毕业设计中常见的问题和解决方案,包括数据库设计、界面美化、性能优化等方面的技巧。这些笔记资料均由经验丰富的程序员撰写,可为您提供宝贵的经验分享和指导。 视频教程:为了帮助您更好地学习和理解Java毕业设计的实现过程,我们还提供了多个视频教程,由专业讲师详细讲解各个项目的开发过程和技术要点。通过观看这些视频教程,您可以更加深入地掌握Java编程和项目开发的技能。 总之,本资源包内容丰富、实用性强,是您完成Java毕业设计的必备宝典。无论您是即将毕业的本科生还是研究生,都得拥有这份宝贵的参考资料。在此,我们诚挚地邀请您加入 平台,与众多技术同行一起交流学习,共同进步!随着Java技术的广泛应用,越来越多的计算机相关专业学生选择Java作为毕业设计的编程语言。为了帮助大家更好地完成毕业设计,我们特地整理了一系列Java毕业设计项目参考资源,包括源代码、MD文档、笔记等等,希望能对您的学习与研究提供有力支持。 项目源代码:涵盖了多个Java毕业设计项目的完整代码,包括登录注册、用户管理、数据增删改查等功能模块

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值