SpringBoot 国际化配置项目实战(三)

springboot 项目中实战演练,功能有参数非空校验、默认国际化、正常返回参数国际化、抛异常后信息国际化、全局异常后继续切面拦截国际化、动态参数国际化等后端各个方面的实战。

1、新建springboot微服务,pom中jar的基本引用

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.14.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>net.sourceforge.nekohtml</groupId>
			<artifactId>nekohtml</artifactId>
		</dependency>
		<dependency>  
		    <groupId>org.springframework.boot</groupId>  
		    <artifactId>spring-boot-devtools</artifactId>  
		    <optional>true</optional>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

2、yml配置文件;

spring:
    thymeleaf:
        encoding=UTF-8
        content-type=text/html
        cache=false
        mode=LEGACYHTML5
    messages:
        encoding: UTF-8
        cache-seconds: 1
        basename: static/i18n/messages

3、properties的创建:

引入配置信息:比如英文版

 


demo.key.null= key not null !!!

1000=hello{0}, your verification code is: {1}

 

4、WebMvcConfigurationSupport 类中接口的重写:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;


/**
 * @author wanghuainan
 * @date 2021/10/13
 * 在URL后面加参数,并且使用默认国际化校验时不需要此类 (如果自定义国际化配置文件一定需要此类)?lang=zh_CN/?lang=zh_US;
 * 如果后端通过请求头接参数:accept-language:zh-CN/en ,则需要调此类,如果不加此类报错:"resultMsg": "{demo.key.null}"(使用默认国际化校验时不需要此类);即上下可能是互斥的
 */
@Configuration
public class ValidatorConfiguration extends WebMvcConfigurationSupport {
    @Autowired
    private MessageSource messageSource;

    @Override
    public Validator getValidator() {
        return validator();
    }

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

5、全局异常的创建:



@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AppExceptionHandler {

    @Autowired
    MessageSource messageSource;

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Object methodArgumentNotValid(HttpServletRequest req, MethodArgumentNotValidException  ex)  {

       // Request r= new Request();源码所示
        //ResultBean result = ResultBean.FAIL;
        ResponseDTO result = new ResponseDTO();
        FieldError fe = ex.getBindingResult().getFieldError();
        String msg = fe.getDefaultMessage();
        Locale locale = LocaleContextHolder.getLocale();
       // LocaleContextHolder.setLocale(Locale.ENGLISH);
        LocaleContextHolder.setLocale(Locale.US);
        locale = LocaleContextHolder.getLocale();
        String message = messageSource.getMessage(fe, LocaleContextHolder.getLocale());
        //String message = messageSource.getMessage(fe, Locale.ENGLISH);
        log.info("---MethodArgumentNotValidException Handler--- ERROR: {}",msg);
        result.setResultMsg(msg);
     //   List<ObjectError> errors =ex.getBindingResult().getAllErrors();
     //   StringBuffer errorMsg=new StringBuffer();
     //   errors.stream().forEach(x -> errorMsg.append(x.getDefaultMessage()).append(";"));
     //   log.error("---MethodArgumentNotValidException Handler--- ERROR: {}", errorMsg.toString());
    //    result.setResultMsg(errorMsg.toString());

        return result;
    }

    /**
            * 自定义异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseDTO handleMgtInvalidParamException(Exception e, HttpServletRequest request) {
        ResponseDTO r = new ResponseDTO(ResponseCodeEnum.FAILED,null);
        return r;
    }
}

6、切面 接口 ResponseBodyAdvice<Object> 的实现:


import com.nandao.i18n.dto.ResponseDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
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.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.servlet.support.RequestContext;

import javax.servlet.http.HttpServletRequest;

/**
 * 这里同时拦截控制层和全局异常类
 */
@ControllerAdvice(basePackages={"com.nandao.i18n.controller","com.nandao.i18n.exception"})
public class I18nResponseBodyAdvice implements ResponseBodyAdvice<Object>{

	private MessageSource messageSource;
	@Value("${spring.messages.basename}")
	private String basename;

	@Value("${spring.messages.cache-seconds}")
	private long cacheMillis;

	@Value("${spring.messages.encoding}")
	private String encoding;

	protected Logger log = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public Object beforeBodyWrite(Object obj, MethodParameter method,
			MediaType type, Class<? extends HttpMessageConverter<?>> converter,
			ServerHttpRequest request, ServerHttpResponse response) {
		try {
			if(obj instanceof ResponseDTO){
				ResponseDTO result = (ResponseDTO) obj;
				String resultCode = result.getResultCode();
				if(resultCode!= null){
					HttpServletRequest req = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
					String i18nMsg = getMessage(req, resultCode);
					if(null !=i18nMsg){
						result.setResultMsg(i18nMsg);
					}else {
						log.info("采用上游的赋值");
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("返回值国际化拦截异常",e);
		}
		return obj;
	}

	@Override
	public boolean supports(MethodParameter arg0,
			Class<? extends HttpMessageConverter<?>> arg1) {
		return true;
	}
	
	/**
	 * 返回国际化的值
	 * @param request
	 * @param key
	 * @return
	 */
	public String getMessage(HttpServletRequest request, String key){
        RequestContext requestContext = new RequestContext(request);
        String value = requestContext.getMessage(key);
        return value;
    }

	/**
	 * 初始化
	 * @return
	 */
	private MessageSource initMessageSource() {
		ReloadableResourceBundleMessageSource messageSource=new ReloadableResourceBundleMessageSource();
		log.info("baseName====>:" + this.basename);
		messageSource.setBasename(basename);
		messageSource.setDefaultEncoding(encoding);
		messageSource.setCacheMillis(cacheMillis);
		return messageSource;
	}

	/**
	 * 设置当前的返回信息,也可以使用
	 * @param request
	 * @param code
	 * @return
	 */
	/*public String getMessage(HttpServletRequest request,String code){
		if(messageSource==null){
			messageSource=initMessageSource();
		}
		Request r = new Request();
		String lauage=request.getHeader("accept-language");
		//默认没有就是请求地区的语言
		Locale locale=null;
		if(lauage==null){
			locale=request.getLocale();
		}
		else if("en".equals(lauage)){
			locale=Locale.ENGLISH;
		}
		else if("ko".equals(lauage)){
			locale=Locale.KOREAN;
		}
		else if("zh-CN".equals(lauage)){
			locale=Locale.CHINA;
		}
		//其余的不正确的默认就是本地的语言
		else{
			locale=request.getLocale();
		}
		String result=null;
		try {
			result = messageSource.getMessage(code, null, locale);
			//result = messageSource.getMessage(code, null, "en_US");
		} catch (NoSuchMessageException e) {
			log.error("Cannot find the error message of internationalization, return the original error message.");
		}
		if(result==null){
			return code;
		}
		return result;
	}
*/


}

7、aspect切面的实现和6类似,二选一方案:


import com.nandao.i18n.constant.ResultEnglishMsg;
import com.nandao.i18n.dto.ResponseDTO;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Objects;

/**
 * @author wanghuainan
 * @date 2021/10/9
 */
@Aspect
@Slf4j
@Component
public class GlobalEnglishMsgAspect {

    private MessageSource messageSource;

    @Value("${spring.messages.basename}")
    private String basename;

    @Value("${spring.messages.encoding}")
    private String encoding;


    @Pointcut("execution(public * com.nandao.i18n.controller.*.*(..))")
    private void pointCut() {
    }

    @Pointcut("execution(public * com.nandao.i18n.exception.*.*(..))")
    private void pointExceptionCut() {
    }

    /**
     * 业务接口请求后统一处理错误状态码
     * @param joinPoint
     * @param returnVal
     */
    @AfterReturning(returning = "returnVal", pointcut = "pointCut()")
    public void doAftereReturning(JoinPoint joinPoint, Object returnVal) {
        commonReturning( joinPoint,  returnVal);
    }

    /**
     * 业务接口请求异常后统一处理错误状态码
     * @param joinPoint
     * @param returnExceptionVal
     */
    @AfterReturning(returning = "returnExceptionVal", pointcut = "pointExceptionCut()")
    public void doAftereExceptionReturning(JoinPoint joinPoint, Object returnExceptionVal) {
        commonReturning( joinPoint,  returnExceptionVal);
    }

    private void commonReturning(JoinPoint joinPoint, Object returnCommonVal) {
        String classMethod = null;
        if (returnCommonVal instanceof ResponseDTO) {
            ResponseDTO r = (ResponseDTO) returnCommonVal;
            /**
             * 如果返回的是错误状态码,则需要判断是否需要转化成英文
             */
            String returnCode = r.getResultCode();
            if (!returnCode.equals("100000")) {
                classMethod = getClassMethodName(joinPoint);
                boolean flag = checkHeaderName();
                if (!flag) {
                    log.info("此接口[{}]返回的msg不需要转化为英文,,错误码[{}]", classMethod,returnCode);
                    return;
                }
                if (ResultEnglishMsg.codeList.contains(returnCode)) {
                    log.info("此接口[{}]特殊处理英文msg,错误码[{}]", classMethod,returnCode);
                    return;
                }
                log.info("此接口[{}]返回的msg需要转化为英文,,错误码[{}]", classMethod,returnCode);
                HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                String i18nMsg = getMessage(req, returnCode);//获取国际化配置
                /**
                 * 英文提升如果为空,则还用原来的中文提升
                 */
                if (Objects.nonNull(i18nMsg)) {
                    r.setResultMsg(i18nMsg);
                } else {
                    log.info("英文提升为空,还用原来的中文提升,错误码code:[{}],接口名[{}]", returnCode, classMethod);
                }
            }
        } else {
            classMethod = getClassMethodName(joinPoint);
            log.info("此接口[{}]返回的不是R对象,没有msg,不需要英文转化", classMethod);
        }
    }

    /**
     * 初始化
     * @return
     */
    private MessageSource initMessageSource() {
        ReloadableResourceBundleMessageSource messageSource=new ReloadableResourceBundleMessageSource();
        log.info("baseName====>:" + this.basename);
        messageSource.setBasename(basename);
        messageSource.setDefaultEncoding(encoding);
        return messageSource;
    }

    /**
     * 设置当前的返回信息
     * @param request
     * @param code
     * @return
     */
    public String getMessage(HttpServletRequest request,String code){
        if(messageSource==null){
            messageSource=initMessageSource();
        }
        String result=null;
        try {
            result = messageSource.getMessage(code, null, request.getLocale());
        } catch (NoSuchMessageException e) {
            log.error("Cannot find the error message of internationalization, return the original error message.[{}]",request.getRequestURI());
        }
        return result;
    }

    /**
     * 判断请求头的参数是否是国际版
     *
     * @return
     */
    private boolean checkHeaderName() {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(ra)) {
            log.info("服务里RequestAttributes对象为空");
            return false;
        }
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getHeader(name);
            if (name.equals("accept-language") && value.equals("en")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取请求的方法名称
     *
     * @param joinPoint
     * @return
     */
    private String getClassMethodName(JoinPoint joinPoint) {
        /**
         * 如果服务中所有的控制层接口名被代理,只能取uri;
         */
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.nonNull(requestAttributes)) {
            HttpServletRequest request = requestAttributes.getRequest();
            return request.getRequestURI();
        }
        //如果是子线程的话,会走下面这种情况
        log.info("classMethod={}", joinPoint.getSignature().getDeclaringTypeName());
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        String className = declaringTypeName.substring(declaringTypeName.lastIndexOf(".") + 1);
        log.info("className={};Method={}", className, joinPoint.getSignature().getName());
        return className + "." + joinPoint.getSignature().getName();
    }

}

8、VO 和控制层代码的实现:

import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.Size;

/**
 * @author wanghuainan
 * @date 2021/10/15
 */
@Data
public class UserInfo {
    //@Size(min = 1, max = 10, message = "姓名长度必须为1到10")
   // @NotEmpty(message = "{user.name.notBlank}")
    @NotEmpty(message = "{demo.key.null}")//必须配置地址,保证不能错:basename: static/i18n/messages
    //@NotEmpty()
    private String name;
}

控制层:

@PostMapping("/validate1")
	@ResponseBody
	public ResponseDTO validate1(@Valid @Validated @RequestBody UserInfo user) throws Exception {
		Map<String,Object> map = new HashMap<String, Object>();
		map.put("key", "这里就是数据了");
         //动态传参
		String message = messageSource.getMessage("1000", new Object[]{"nandao","128"}, LocaleContextHolder.getLocale());
		System.out.println("国际化后的参数"+message);
		if("nandao".equals(user.getName())){
			throw new Exception("抛出异常!!!");
		}
		return new ResponseDTO(ResponseCodeEnum.SUCCESS,map);
	}

 

9、启动服务后依次访问接口验证:http://localhost:8080/api/validate1

多角度,正常、异常、参数非空校验、动态传参、两种切面均可以测试。

 到此,参数国际化实战分享完毕,核心伪代码都贴出来了,有不明白的地方可以留言关注,积极反馈!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值