无缝对接多语言:参数校验的终极指南(一)!

news2024/12/23 13:43:04

前言

  在此之前,写过在两篇文章,是关于如何在 SpringBoot 内实现统一参数校验和自定义校验注解的。毕竟作为后端来讲,对于前端传来的数据,需要保持高度的警惕。避免出现异常数据,导致系统异常。统一参数校验和自定义校验注解,可以帮助我们更加优雅和严格的完成参数校验,减少出错的概率。

    /**
     * 账户名
     */
    @Email(message = "邮箱格式有误")
    @NotBlank(message = "账户名称不能为空")
    @ApiModelProperty(notes = "账户名", required = true)
    private String accountName;

  随着业务的发展碰上了多语言,多区域,原本的参数错误提示语就不太够用了。当 APP 切换到别的区域,比如美国,接口出错提示语还是中文这就不太行了。所以我们今天就要解决它。

准备工作

  我们可以用 idea 初始化一个最基本的项目,然后配置一下统一参数校验。如下图所示:

LoginBo

@Data
public class LoginBo {

    /**
     * 账户名
     */
    @NotBlank(message = "账户名称不能为空")
    private String accountName;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空")
    private String password;

}

ResultVo

public class ResultVo<T> {

    private String code;
    private String msg;
    private T data;


    public ResultVo() {
    }

    public ResultVo(String code, String msg) {
        this(code, msg, null);
    }

    public ResultVo(String code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    // 省略一些方法
}

TestController

@RestController
@RequestMapping("/test")
public class TestController {

    @PostMapping("/demo")
    public ResultVo<Void> demo(@RequestBody @Validated LoginBo bo) {
        System.out.println(bo);
        return ResultVo.success();
    }
}

GlobalExceptionHandler

@Component
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 参数校验不通过
     *
     * @param e BindException
     * @return ResultVo<Void>
     */
    @ExceptionHandler(BindException.class)
    public ResultVo<Void> handlerBindException(BindException e) {
        return ResultVo.failure(this.buildMsg(e.getBindingResult()));
    }

    /**
     * 参数校验不通过
     *
     * @param e MethodArgumentNotValidException
     * @return ResultVo<Void>
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultVo<Void> handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        return ResultVo.failure(buildMsg(e.getBindingResult()));
    }

    /**
     * 构建参数错误提示信息
     *
     * @param bindingResult BindingResult
     * @return String
     */
    private String buildMsg(BindingResult bindingResult) {
        StringBuilder builder = new StringBuilder(32);
        for (FieldError error : bindingResult.getFieldErrors()) {
            builder.append(", [").append(error.getField()).append(":").append(error.getDefaultMessage()).append("]");
        }
        return builder.substring(2);
    }

}

实现方式

  首先明确一下我们的需求点:就是针对不同的语言,接口对应的错误提示语要不一样。这就意味着错误提示语是动态的不能写死。实现思路如下:

  1. 我们可以先针对不同的语言,翻译好对应的错误提示语,并生成相应的配置文件。
  2. 让注解内的 message 指向对应文件内的错误提示语。

配置文件

  这里其实是使用了 Spring Boot 提供的国际化支持来配置多语言提示语。首先,在资源文件中创建多个语言的属性文件,例如 messages.properties 表示默认的英文提示语,messages_zh_CN.properties 表示中文提示语。

  1. 中文配置文件:messages_zh_CN
account.name=账户名称不能为空
password=密码不能为空
  1. 英文配置文件:messages_en_US
account.name=account name cannot be empty
password=password cannot be empty

错误提示语指向配置文件

@Data
public class LoginBo {

    /**
     * 账户名
     */
    @NotBlank(message = "{account.name}")
    private String accountName;

    /**
     * 密码
     */
    @NotBlank(message = "{password}")
    private String password;

}

演示一下

  理想很丰满,现实很骨感。貌似并没有生效,而是把我们的占位符,直接当提示语输出了。

  不要慌,这个其实是没有指定对应的配置文件,让我们配置一下,先设置为中文的。

@Configuration
public class ValidationConfig {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        // 先设置中文
        messageSource.setBasename("messages_zh_CN");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource);
        return validatorFactoryBean;
    }
}

再次尝试

  直接就成功了,那改成英文,让我们再试一下。

@Configuration
public class ValidationConfig {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages_en_US");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource);
        return validatorFactoryBean;
    }
}

改进一下

  通过上面的示例可以看到,虽然是实现了动态,但是还不够优雅。切换的时候,需要修改对应的配置代码。所以让我们改进下,把这部分也做成配置,在启动的时候进行指定就好了,这样方便在部署不同区域的时候可以动态进行配置。

  1. 增加默认语言配置
# 服务端口
server:
  port: 10000

# 配置默认语言
app:
  default:
    language: zh_CN
  1. 读取配置文件的默认语言
@Slf4j
@Configuration
public class ValidationConfig {

    @Value("${app.default.language}")
    private String defaultLanguage;

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages_" + defaultLanguage);
        messageSource.setDefaultEncoding("UTF-8");
        log.info("Message Source init suc -> lang:{}", defaultLanguage);
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource);
        return validatorFactoryBean;
    }
}

  再次测试一下,结果如下:

敲黑板

  虽然上面实现了功能,但是其实是违反了设计原则的,为什么这样说呢?我们可以看看setBasename的注释,看看它是如何使用的。如下图所示:

译文

  从注释总可以发现,basename 需要遵循不指定文件扩展名或语言代码的基本 ResourceBundle 约定。很明显我们违反了。

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages_en_US");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

  所以正确的方式应该是这样的。

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

  这个时候你可能会有疑问。如果不进行指定,那系统咋知道选用哪个配置文件呢?这个问题的答案就是,上面提到的 basename 需要遵循 ResourceBundle 约定。

ResourceBundle

  ResourceBundle 是 Java 标准库中的一个类,用于加载和管理国际化资源。它提供了一种机制来加载不同语言和区域的资源文件,并根据当前的 Locale 进行国际化处理。它提供了以下主要功能:

  1. 选择合适的资源文件:根据给定的 Locale,ResourceBundle 可以选择最匹配的资源文件。如果找不到完全匹配的资源文件,它会尝试找到默认的资源文件或向上回退到更通用的语言环境。
  2. 加载资源文件:ResourceBundle 会负责加载属性文件,并将其缓存在内存中,以便在需要时进行快速访问。
  3. 获取国际化消息:通过资源文件中定义的键,您可以使用 ResourceBundle 获取相应的国际化消息。ResourceBundle 将根据当前的 Locale 自动选择正确的资源文件,并返回与给定键对应的消息。

Locale

  在 Spring Boot 中,默认的 Locale 是根据操作系统的默认语言环境来确定的。它是通过调用 Locale.getDefault() 方法获取的。Locale.getDefault() 方法返回的是 JVM 运行环境的默认 Locale。

  如果您在操作系统中设置了特定的默认语言,那么 Spring Boot 应用程序将使用该默认语言作为默认的 Locale。如果操作系统没有明确设置默认语言,那么它可能会使用 JVM 的默认语言设置。请注意,如果您在 Spring Boot 应用程序中显式设置了其他的 Locale,它将覆盖操作系统的默认设置。

小结一下

  看到这里,我们可以对上面的问题小结一下了。为什么只需要设置 basename 即可?由于 basename 会遵循 ResourceBundle 约定。ResourceBundle 会根据 Spring Boot 获取到 Locale 选择来匹配资源文件。

  并且由于 ResourceBundle 的特点,如果找不到完全匹配的资源文件,它会尝试找到默认的资源文件或向上回退到更通用的语言环境。如果还找不到,那就只能把{xx.xxx}当提示语输出了,也不会影响系统运行。所以最后配置就变成这样了:

@Slf4j
@Configuration
public class ValidationConfig {


    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource);
        return validatorFactoryBean;
    }

}

  到了这里,貌似已经差不多了。但是有个问题,spring boot 默认是取操作系统的 Locale,如果取不到再取 JVM 的。假如服务器配置的是英文,接口需要返回中文,这不就有问题了吗。毕竟找运维大哥去修改还不如自己通过代码处理。

  处理方式如下:我们可以从配置文件读取默认语言配置,然后生成一个LocaleResolver

@Slf4j
@Configuration
public class ValidationConfig {

    @Value("${app.default.language}")
    private String defaultLanguage;

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        resolver.setDefaultLocale(new Locale(defaultLanguage));
        return resolver;
    }

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource);
        return validatorFactoryBean;
    }

}

  再次测试一下,结果如我们所愿:

总结

  该功能的实现主要依托于Spring Boot多语言。实现思路是:预先生成好对应的多语言配置文件,在需要实现多语言的地方跟配置文件进行关联,然后在设置对应Locale即可。

  当前我们只是实现了一个简单的案例。适用的场景是:服务部署在不同的区域,返回对应区域语言的提示语。

  假如我们的需求在进阶一点呢?在同一个区域,需要根据请求头内x-lang的标记语言类型,动态返回呢?并且配置文件不想写死在本地,比如放在nacos或者mysql内实现热更新呢?我们下期继续聊。

结尾

  如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

  我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/584382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

现在的00后,真是卷死了呀,辞职信已经写好准备提交了·····

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;四月份春招我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪22K&#xff0c;都快接近我了。 后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了…

自从用了 EasyExcel,导入导出 Excel 更简单了!

EasyExcel 在做excel导入导出的时候,发现项目中封装的工具类及其难用,于是去gitHub上找了一些相关的框架,最终选定了EasyExcel。之前早有听闻该框架&#xff0c;但是一直没有去了解&#xff0c;这次借此学习一波&#xff0c;提高以后的工作效率。 实际使用中&#xff0c;发现…

服了呀,00后怎么这么卷....

现在的小年轻真的卷得过分了。前段时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…

FDA辅料数据库-在线查询网址

在药物的制剂研发过程中&#xff0c;辅料是不可或缺的。然而&#xff0c;在使用辅料时需要明确其安全使用量&#xff0c;这并非易事。因此&#xff0c;美国FDA 辅料数据库&#xff08;IID&#xff09;成为了一个重要的参考标准&#xff0c;对于制剂开发提供了帮助。 如果研发人…

《汇编语言》- 读书笔记 - 第7章- 更灵活的定位内存地址的方法

《汇编语言》- 读书笔记 - 第7章- 更灵活的定位内存地址的方法 7.1 and 和 or 指令7.2 关于 ASCII 码7.3 以字符形式给出的数据程序 7.1 7.4 大小写转换的问题7.5 [bxidata] &#xff08;变量 固定偏移量&#xff09;问题 7.1 7.6 用[bxidata]的方式进行数组的处理7.7 SI 和 D…

软件测试测试环境搭建很难?一天学会这份测试环境搭建教程

如何搭建测试环境&#xff1f;这既是一道高频面试题&#xff0c;又是困扰很多小伙伴的难题。因为你在网上找到的大多数教程&#xff0c;乃至在一些培训机构的课程&#xff0c;都不会有详细的说明。 你能找到的大多数项目&#xff0c;是在本机电脑环境搭建环境&#xff0c;或是…

Linux服务器上如何安装OpenCV的库?

Linux上安装OpenCV其实挺简单的。对于Python来说&#xff0c;可以直接使用pip进行安装&#xff0c;如&#xff1a; pip3 install opencv-python 当然&#xff0c;如果你是想在C或者Java内作为外部包使用&#xff0c;你可以考虑编译安装。 安装依赖 首先是依赖安装问题&#…

LED屏控制卡

LED屏控制卡&#xff08;LED Screen Control Card&#xff09;是一种用于控制和管理LED显示屏的关键设备。它通常是一个硬件设备&#xff0c;具有处理器、存储器、接口和软件功能&#xff0c;用于接收、解码和显示图像、视频和其他多媒体内容。 以下是LED屏控制卡的一些重要特点…

JVM (基础概念、类加载过程、垃圾回收算法)

目录 一、JVM 是什么 二、JVM 运行流程 三、Java运行时数据区 1、程序计数器&#xff08;线程私有&#xff09; 2、栈区&#xff08;线程私有&#xff09; 3、堆 4、方法区 四、OOM内存溢出和内存泄漏 1、OOM内存溢出 2、内存泄漏 五、类加载过程 1、加载 2、连接…

PMP课堂模拟题目及解析(第14期)

131. 项目经理正在制定干系人参与计划&#xff0c;并识别到一位权力等级较高但在项目中兴趣较低的干系人&#xff0c;项目经理应该如何对待该干系人&#xff1f; A. 重点管理 B. 随时告知 C. 监督 D. 令其满意 132. 项目经理识别到项目干系人具有明显不同的需求和期望。…

不合格机器人工程专业讲师笔记-230529-

工作八年&#xff0c;最大的遗憾&#xff0c;就是对不起学生&#xff0c;对不起同事&#xff0c;对不起领导&#xff0c;各项工作都没有做好。 但是由于个人水平低&#xff0c;能力差&#xff0c;唯一能做的就是在忏悔中不断总结&#xff0c;避免一次又一次失败。 一万句&…

别让测试岗位的坑太大,10年老鸟亲身经历告诉你如何避开陷阱

测试岗位一直是IT行业中备受争议的一个职业&#xff0c;有人看重其重要性&#xff0c;有人则认为这是个巨坑。如果你也对测试岗位存在疑虑和担忧&#xff0c;那么这篇文章一定能帮到你&#xff01; 作者是一位在测试领域摸爬滚打了10年的老鸟&#xff0c;他深刻地理解了测试工…

行业报告 | 智能制造在中国—中国机器视觉产业链现状分析

文 | BFT机器人 导语 Introduction 智能制造装备是指具有感知、分析、推理、决策、控制功能的制造装备&#xff0c;它是先进制造技术、信息技术和智能技术的集成和深度融合&#xff0c;体现了制造业智能化、数字化和网络化的发展要求。智能制造装备的水平已成为当今衡量一个国家…

数据类岗位面试随想录

数据分析或者是偏向数据分析的数据开发岗&#xff0c;要求无非就是SQL、Python和业务相关的问题。 1 SQL问答 基本这些问题和期末考试的难度比&#xff0c;是简单的。和学校所教的比&#xff0c;基本超纲的问题只会有窗口函数。这一部分面试官一般不会问你难的问题&#xff0c…

通用支付系统设计

支付永远是一个公司的核心领域&#xff0c;因为这是一个有交易属性公司的命脉。那么&#xff0c;支付系统到底长什么样&#xff0c;又是怎么运行交互的呢?抛开带有支付牌照的金融公司的支付架构&#xff0c;下述链路和系统组成基本上符合绝大多数支付场景。其实整体可以看成是…

【人脸识别】insightface 使用记录和搭建服务注意点 从0到1

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1.开始1.1 前置1.2 再次运行&#xff0c;人脸检测跑通 前言 人脸识别项目&#xff0c;再走一遍。之前是公司老人留下的&#xff0c;没部署过&#xff0c;没交付…

【HMS Core】Health Kit关于获取历史数据问题

【问题描述1】 应用已经开通了历史数据访问权限&#xff0c;同时用户在授权页面已经勾选了”历史数据“项&#xff0c;然后我们是调用healthkit的rest接口查询健康数据&#xff0c;那么是否用户授权之前一年的健康数据都能被查询到呢&#xff1f; 【解决方案】 当用户授予应用…

5月底了,让我看看有多少准备跳槽的····

前两天跟朋友感慨&#xff0c;今年的铜三铁四、裁员导致好多人都没拿到offer!现在已经5月底了&#xff0c;具体金九银十只剩下3个月。 对于想跳槽的职场人来说&#xff0c;绝对要从现在开始做准备了。这时候&#xff0c;很多高薪技术岗、管理岗的缺口和市场需求也出来了。 所…

字节跳动测试岗,3面都过了,HR告诉我这个原因被刷了...

说在前面 面试时最好不要虚报工资。本来字节跳动是很想去的&#xff0c;几轮面试也通过了&#xff0c;最后没offer&#xff0c;自己只想到下面几个原因&#xff1a; 虚报工资&#xff0c;比实际高30%&#xff1b; 有更好的人选&#xff0c;这个可能性不大&#xff0c;我看还在…

6个月的测试,来面试居然要18K,我一问连8K都不值

2023年4月份我入职了深圳某家创业公司&#xff0c;刚入职还是很兴奋的&#xff0c;到公司一看我傻了&#xff0c;公司除了我一个自动化测试&#xff0c;公司的测试人员就只有2个开发3个前端1个测试还有2个UI&#xff0c;在粗略了解公司的业务后才发现是一个从零开始的项目&…