Springboot2.x全局异常处理+参数校验+多语言

demo地址

1、背景

当一个项目需要支持国际化时,如何在不改变后端代码的基础上增加语言?
当业务处理流程复制的时候,每种异常的抛出是否都需要try-catch?
各种请求参数的校验是否都需要一个一个去写?
其实我们会发现这些问题都可以在框架层很轻松的解决掉,减少大量冗余代码的同时,增强了系统的可扩展性和鲁棒性。

2、多语言配置

配置messageSource,指定多语言文件的路径

@Configuration
public class MessageSourceConfig implements WebMvcConfigurer {

    @Value("${i18n.meessages.path:}")
    private String messagesPath;

    @Bean(name = "messageSource")
    @ConditionalOnMissingBean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource rbms = new ReloadableResourceBundleMessageSource();
        if(!StringUtils.isEmpty(messagesPath)){
            //设置path
            rbms.addBasenames(messagesPath.split(","));
        }
        rbms.setDefaultEncoding("UTF-8");
        rbms.setUseCodeAsDefaultMessage(true);
        rbms.setFallbackToSystemLocale(false);
        return rbms;
    }

    /**
     * 使用session存储语言信息
     *
     * @return
     */
//    @Bean
//    public LocaleResolver localeResolver() {
//        SessionLocaleResolver slr = new SessionLocaleResolver();
//        slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
//        return slr;
//    }

    /**
     * 使用cookie存储语言信息,默认语言-简体中文
     *
     * @return
     */
    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver slr = new CookieLocaleResolver();
        slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return slr;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("language");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

多语言的配置方式有很多种,需要根据项目需要选择,CookieLocaleResolver是将语言信息缓存在cookie中,请求时设置参数language即可实现语言的切换。还可换为SessionLocaleResolver使用Session缓存。LocaleChangeInterceptor只是将language取出来缓存到指定的地方。
获取当前的多语言有静态方法可获取:
LocaleContextHolder.getLocale()
实际项目可能语言信息是存在header中的,除messageSource的配置外,其他的都可根据实际情况配置,修改后只需将I18nUtils中的getLocale改为实际可获取多语言的方法即可。
获取多语言的静态工具

public class I18nUtils {

    private static final Logger logger = LoggerFactory.getLogger(I18nUtils.class);

    /**
     * 获得多语言内容,默认根据当前用户的Local获取
     * @param key 多语言key
     * @return 翻译后的值,如获取不到则返回key
     */
    public static String getMessage(String key){
        return getMessage(key,new Object[]{});
    }

    /**
     * 获取多语言内容--带默认值,默认根据当前用户的Local获取
     * @param key 多语言key
     * @param defaultMsg 默认返回值
     * @return 翻译后的值,获取不到返回默认值
     */
    public static String getMessage(String key,String defaultMsg){
        return getMessage(key,defaultMsg,new Object[]{});
    }


    /**
     * 获得多语言内容--带参数,默认根据当前用户的Local获取
     * @param key 多语言key
     * @param params 参数
     * @return 翻译后的值,如获取不到则返回key
     */
    public static String getMessage(String key,Object[] params){
        Locale locale = getLocale();
        if(params == null){
            params = new Object[]{};
        }
        return getMessage(key,params,locale);

    }

    /**
     * 获得多语言内容--带参数--带默认值,默认根据当前用户的Local获取
     * @param key 多语言key
     * @param defaultMsg 默认返回值
     * @param params 参数
     * @return 翻译后的值,获取不到返回默认值
     */
    public static String getMessage(String key,String defaultMsg,Object[] params){
        Locale locale = getLocale();
        return getMessage(key,params,defaultMsg,locale);
    }

    /**
     * 不带参数
     * @param key 多语言key
     * @param locale 语言信息
     * @return 翻译后的值
     */
    public static String getMessage(String key,Locale locale){
        return getMessage(key,new Object[]{},locale);
    }


    /**
     * 获取多语言数据
     * @param key 多语言key
     * @param params 参数
     * @param locale 语言信息
     * @return 翻译后的值
     */
    public static String getMessage(String key,Object[] params,Locale locale){
        try{
            return AppContext.getContext().getMessage(key,params,locale);
        }catch (Exception e){
            logger.error("get locale msg error" ,e);
        }
        return key;
    }

    /**
     * 获取多语言数据
     * @param key 多语言key
     * @param params 参数
     * @param defaultMsg 默认返回值
     * @param locale 语言信息
     * @return 翻译后的值
     */
    public static String getMessage(String key, Object[] params, String defaultMsg, Locale locale) {
        return AppContext.getContext().getMessage(key,params,defaultMsg,locale);
    }

    /**
     * 获取当前语言
     *
     * @return
     */
    private static Locale getLocale(){
        return LocaleContextHolder.getLocale();
    }
}

application.properties中配置多语言文件路径

#多语言文件路径
i18n.meessages.path=classpath:messages/message

3、参数校验框架配置

配置快速失败模式

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }


    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        /**设置validator模式为快速失败返回*/
        postProcessor.setValidator(validator());

        return postProcessor;
    }
}

原生的hibernate-validator功能已非常丰富,无语额外配置。

4、全局异常处理配置

@RestControllerAdvice
public class CustomExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);


    /**
     * get请求的参数校验失败
     *
     * @param exception
     * @return
     */
    @ExceptionHandler
    public BaseResponse handle(ConstraintViolationException exception) {
        logger.error("param validation error", exception);
        Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
        if (!CollectionUtils.isEmpty(violations)) {
            String message = violations.iterator().next().getMessage();
            logger.error("validate error,message={0}", message);
            return BaseResponseBuilder.createResponse(DefaultErrorCode.VALIDATE_ERROR, message);
        }

        return BaseResponseBuilder.createResponse(DefaultErrorCode.ERROR);
    }

    /**
     * post请求校验失败
     *
     * @param exception
     * @return
     */
    @ExceptionHandler
    public BaseResponse handle(MethodArgumentNotValidException exception) {
        logger.error("param validation error", exception);
        List<ObjectError> errors = exception.getBindingResult().getAllErrors();
        String code = DefaultErrorCode.VALIDATE_ERROR.getCode();
        String msg = null;
        if (!CollectionUtils.isEmpty(errors)) {
            int first = 0;
            for (int i = 0; i < errors.size(); i++) {
                ObjectError error = errors.get(i);
                String message = error.getDefaultMessage();
                String field = null;
                if (error instanceof FieldError) {
                    field = ((FieldError) error).getField();
                }

                message = I18nUtils.getMessage(message);
                if (i == first) {

                    //取一个错误信息传给前端
                    msg = message;
                }
                logger.error("validate error, field={0}, message={1}", field, message);
            }
        }
        return new BaseResponse(code, msg);
    }

    @ExceptionHandler
    public BaseResponse handle(CustomBusinessException e) {
        //errorCode处理
        String code = e.getCode();
        logger.error("business error", e);
        return BaseResponseBuilder.createResponse(code, e.getMessage(), e.getParams());
    }

    /**
     * 其他没有处理的错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler
    public BaseResponse<Object> handle(Exception e) {
        logger.error("unkown error", e);
        return BaseResponseBuilder.createResponse(DefaultErrorCode.ERROR);
    }
}

5、测试

5.1 测试业务异常的抛出

直接在接口中抛出业务异常,业务异常上配置多语言信息

	@GetMapping("/test3")
    public BaseResponse test3(){
        throw new CustomBusinessException(BusinessErrorCode.BUSINESS_ERROR_1);
    }
public enum BusinessErrorCode implements IErrorCode{

    BUSINESS_ERROR_1("20001", "business.error.test"),

   ;

    ......

配置多语言中的中文翻译

success=成功
unkown.error=未知系统错误
validate.error=参数校验错误
param1.empty.error=param1不能为空
business.error.test=业务异常

在这里插入图片描述

5.2 测试请求参数校验

    @PostMapping("/test2")
    public BaseResponse test2(@RequestBody @Valid RequestBo requestBo){
        return BaseResponseBuilder.createResponse();
    }
@Data
public class RequestBo {

    @NotEmpty(message = "param1.empty.error")
    private String param1;

    private Integer param2;
}

在这里插入图片描述

5.3 多语言切换

在这里插入图片描述
在这里插入图片描述

发布了26 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_36142042/article/details/104956225